-
1
class ApplicationController < ActionController::Base
-
# Prevent CSRF attacks by raising an exception.
-
# For APIs, you may want to use :null_session instead.
-
# protect_from_forgery with: :exception
-
# acts_as_token_authentication_handler_for User
-
-
1
before_filter :cors_set_access_control_headers
-
1
before_filter :get_notifications
-
-
1
def home
-
end
-
-
1
def get_notifications
-
23
@allActivities = PublicActivity::Activity.all
-
-
23
if @allActivities.count > 0 and current_user
-
16
@allActivities = @allActivities.where("owner_id = ? or recipient_id = ?", current_user.id, current_user.id).order(:created_at).reverse_order
-
-
54
@allOwnersN = @allActivities.map { |activity| ::User.find(activity.owner_id)}
-
54
@allRecipientsN = @allActivities.map { |activity| ::User.find(activity.recipient_id)}
-
54
@allExchangesN = @allActivities.map { |activity| ::Exchange.find(activity.exchange_id)}
-
54
@allPostsN = @allActivities.map { |activity| ::Post.find(activity.post_id)}
-
end
-
-
end
-
-
1
protected
-
-
1
def handle_options_request
-
head(:ok) if request.request_method == "OPTIONS"
-
end
-
-
1
def json_request?
-
request.format.json?
-
end
-
-
1
def cors_set_access_control_headers
-
23
headers['Access-Control-Allow-Origin'] = 'http://localhost'
-
23
headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, DELETE, OPTIONS'
-
23
headers['Access-Control-Allow-Headers'] = '*, X-Requested-With, X-Prototype-Version, X-CSRF-Token, Content-Type'
-
23
headers['Access-Control-Max-Age'] = "1728000"
-
end
-
-
1
def render_errors(errorsArr)
-
9
return render :json => {:status => -1, :errors => errorsArr}, :status => 404
-
end
-
-
end
-
1
class ExchangesController < ApplicationController
-
1
skip_before_action :verify_authenticity_token
-
1
skip_before_filter :authenticate_user!, :only => [:statuses]
-
1
skip_before_filter :authenticate_user_from_token!, :only => [:statuses]
-
-
1
respond_to :json
-
# POST /api/v1/exchanges
-
# Post an exchange
-
1
def create
-
6
@borrower = current_user
-
-
6
begin
-
6
@post = ::Post.find(exchange_params[:post_id])
-
rescue ActiveRecord::RecordNotFound
-
return render_errors(['Post does not exist.'])
-
end
-
-
6
begin
-
6
@lender = ::User.find(@post.user_id)
-
rescue ActiveRecord::RecordNotFound
-
return render_errors(['Lender does not exist.'])
-
end
-
-
6
if @borrower.id == @lender.id
-
return render_errors(["Borrower and lender cannot be the same."])
-
end
-
-
6
existingExchanges = ::Exchange.where(post: @post, lender: @lender, borrower: @borrower, status:1)
-
6
if existingExchanges.count > 0
-
1
ids = existingExchanges.collect(&:id).to_sentence
-
1
return render_errors(["Transactions already exist for this specific post, lender, and borrower. The transaction ids are: #{ids}."])
-
end
-
-
5
@exchange = ::Exchange.new(post: @post, lender: @lender, borrower: @borrower, status: 1)
-
-
5
if @exchange.save
-
5
newExchange = update_created_and_updated_at(@exchange)
-
# Changing status to "On Hold"
-
5
@exchange.create_activity(action: :create_exchange, owner: @lender, recipient: @borrower, post_id: @post.id, exchange_id: @exchange.id)
-
5
@post.update_attributes(:status => '2')
-
end
-
end
-
-
# GET /api/v1/exchanges(.:format)
-
# Gets all posts
-
1
def index
-
4
@allExchanges = ::Exchange.all.order(:created_at).reverse_order
-
-
4
if params.has_key?(:filter)
-
3
filter = params[:filter].to_s
-
3
if filter == 'lender'
-
1
@allExchanges = @allExchanges.where(lender_id: current_user.id)
-
elsif filter == 'borrower'
-
1
@allExchanges = @allExchanges.where(borrower_id: current_user.id)
-
else
-
1
return render_errors(["Filter has to be either lender or borrower."])
-
end
-
else
-
1
@allExchanges = @allExchanges.where("lender_id = ? or borrower_id = ?", current_user.id, current_user.id)
-
end
-
-
3
exchangeArray = Array.new
-
-
3
@allExchanges.each do |exchange|
-
2
newExchange = update_created_and_updated_at(exchange)
-
2
exchangeArray.push newExchange
-
end
-
-
3
render :json => {:status => 1, :exchange => exchangeArray}
-
end
-
-
# GET /api/v1/exchanges/:id(.:format)
-
# Gets one post
-
1
def show
-
3
begin
-
3
@oneExchange = ::Exchange.find(params[:id])
-
1
rescue ActiveRecord::RecordNotFound
-
1
return render_errors(["Couldn't find exchange because exchange does not exist."])
-
else
-
2
if @oneExchange.borrower_id == current_user.id or @oneExchange.lender_id == current_user.id
-
1
newExchange = update_created_and_updated_at(@oneExchange)
-
1
return render :json => {:status => 1, :exchange => newExchange}
-
else
-
1
return render_errors(["User is not authorized to see this exchange."])
-
end
-
end
-
end
-
-
# PUT /api/v1/exchanges/:id/update_status
-
1
def update_status
-
-
9
begin
-
9
@exchange = ::Exchange.find(params[:id])
-
rescue ActiveRecord::RecordNotFound
-
1
return render_errors(['Exchange does not exist.'])
-
end
-
-
8
@borrower = ::User.find(@exchange.borrower_id)
-
-
8
whats_available = "To query available statuses, call /api/v1/exchanges/statuses."
-
-
8
begin
-
8
status = Integer(params[:status])
-
rescue ArgumentError
-
1
return render_errors(["Status must be an integer. #{whats_available}"])
-
end
-
-
7
if !status.between?(1, 5)
-
1
return render_errors(["Please pick a status from 1 to 5. #{whats_available}"])
-
end
-
-
6
status = params[:status].to_i
-
-
6
if status == 1
-
1
return render_errors(["Setting an exchange to 'Pending' is not allowed. #{whats_available}"])
-
end
-
-
5
if status == @exchange.status
-
1
return render_errors(["The status of this exchange is already #{status}. #{whats_available}"])
-
end
-
-
4
begin
-
4
@post = ::Post.find(@exchange.post_id)
-
rescue ActiveRecord::RecordNotFound
-
return render_errors(["The post that this exchange is linked to is invalid. The post that you linked to is #{post.id}."])
-
end
-
-
4
case status
-
when 2 # Exchange Accepted
-
1
@post.update_attributes(:status => 3) # Assuming post status = 3 means it's borrowed
-
when 3 # Exchange Rejected
-
1
@post.update_attributes(:status => 1) # Assuming post status = 1 means it's available
-
when 4 # Exchange Completed
-
1
@post.update_attributes(:status => 1) # Assuming post status = 1 means it's available
-
when 5 # Exchange Cancelled
-
1
@post.update_attributes(:status => 1) # Assuming post status = 1 means it's available
-
end
-
-
# To save in activity table for notifications
-
4
begin
-
4
@owner = User.find(@post.user_id)
-
rescue ActiveRecord::RecordNotFound
-
return render_errors(["The post belongs to no one. The post that you linked to is #{@post.user_id}."])
-
end
-
-
4
if status == 4
-
1
@exchange.create_activity(action: :exchange_completed, owner: @owner, recipient: @borrower, post_id: @post.id, exchange_id: @exchange.id)
-
else
-
3
@exchange.create_activity(action: :update_status, owner: @owner, recipient: @borrower, post_id: @post.id, exchange_id: @exchange.id, parameters: {from_status: @exchange.status, to_status: status})
-
end
-
-
4
@exchange.update_attributes(status: status)
-
-
# render :json => {:status => 1, :exchange => @exchange}
-
-
end
-
-
# GET /api/v1/exchanges/statuses
-
# Gets all exchange statuses
-
1
def statuses
-
1
render :json => {:status => 1, :categories => {"1" => "Pending", "2" => "Accepted", "3" => "Rejected", "4" => "Completed", "5" => "Cancelled"}}, :status => 200
-
return
-
end
-
-
1
private
-
1
def exchange_params
-
6
params.permit(:post_id)
-
end
-
-
1
def update_created_and_updated_at(exchange)
-
8
newExchange = ActiveSupport::JSON.decode exchange.to_json
-
8
newExchange['created_at'] = exchange.created_at.to_f
-
8
newExchange['updated_at'] = exchange.updated_at.to_f
-
8
return newExchange
-
end
-
-
end
-
1
module ActivitiesHelper
-
end
-
1
module ApplicationHelper
-
end
-
1
module ExchangeHelper
-
end
-
1
module ReviewHelper
-
end
-
1
module StaticHelper
-
end
-
1
class Exchange < ActiveRecord::Base
-
1
include PublicActivity::Model # For tracking updates
-
-
1
attr_accessor :post_description
-
-
1
belongs_to :borrower, :class_name => 'User', required: true
-
1
belongs_to :lender, :class_name => 'User', required: true
-
1
belongs_to :post, :class_name => 'Post', required: true
-
1
has_many :reviews
-
-
1
validates :status, presence: true, numericality: true, :inclusion => {:in => 1..5, :message => "must be an integer between 1 and 5"}
-
end
-
1
class Post < ActiveRecord::Base
-
1
belongs_to :user
-
1
has_many :exchanges
-
1
has_many :reviews
-
-
1
geocoded_by :address
-
1
before_validation :geocode
-
1
def address
-
16
[street, city, state].compact.join(', ')
-
end
-
1
validates :street, presence: true
-
1
validates :city, presence: true
-
1
validates :state, presence: true
-
-
1
validates :title, presence: true, length: {maximum: 64}
-
1
validates :latitude, presence: true, numericality: true, :inclusion => {:in => -180..180}
-
1
validates :longitude, presence: true, numericality: true, :inclusion => {:in => -180..180}
-
1
validates :description, presence: true, length: {maximum: 4098}
-
1
validates :price, presence: true, :format => { :with => /\A\d+(?:\.\d{0,2})?\z/ }, :numericality => {:greater_than => 0}
-
1
validates :security_deposit, presence: true, :format => { :with => /\A\d+(?:\.\d{0,2})?\z/ }, :numericality => {:greater_than => 0}
-
1
validates :status, presence: true, numericality: true, :inclusion => {:in => 1..4, :message => "must be an integer between 1 and 4"}
-
1
validates :category, presence: true, numericality: true, :inclusion => {:in => 1..11, :message => "must be an integer between 1 and 11"}
-
end
-
1
module ActionMailer
-
# Provides helper methods for testing Action Mailer, including #assert_emails
-
# and #assert_no_emails
-
1
module TestHelper
-
# Asserts that the number of emails sent matches the given number.
-
#
-
# def test_emails
-
# assert_emails 0
-
# ContactMailer.welcome.deliver_now
-
# assert_emails 1
-
# ContactMailer.welcome.deliver_now
-
# assert_emails 2
-
# end
-
#
-
# If a block is passed, that block should cause the specified number of
-
# emails to be sent.
-
#
-
# def test_emails_again
-
# assert_emails 1 do
-
# ContactMailer.welcome.deliver_now
-
# end
-
#
-
# assert_emails 2 do
-
# ContactMailer.welcome.deliver_now
-
# ContactMailer.welcome.deliver_now
-
# end
-
# end
-
1
def assert_emails(number)
-
if block_given?
-
original_count = ActionMailer::Base.deliveries.size
-
yield
-
new_count = ActionMailer::Base.deliveries.size
-
assert_equal number, new_count - original_count, "#{number} emails expected, but #{new_count - original_count} were sent"
-
else
-
assert_equal number, ActionMailer::Base.deliveries.size
-
end
-
end
-
-
# Assert that no emails have been sent.
-
#
-
# def test_emails
-
# assert_no_emails
-
# ContactMailer.welcome.deliver_now
-
# assert_emails 1
-
# end
-
#
-
# If a block is passed, that block should not cause any emails to be sent.
-
#
-
# def test_emails_again
-
# assert_no_emails do
-
# # No emails should be sent from this block
-
# end
-
# end
-
#
-
# Note: This assertion is simply a shortcut for:
-
#
-
# assert_emails 0
-
1
def assert_no_emails(&block)
-
assert_emails 0, &block
-
end
-
end
-
end
-
1
module ActionController
-
1
module Testing
-
1
extend ActiveSupport::Concern
-
-
1
include RackDelegation
-
-
# TODO : Rewrite tests using controller.headers= to use Rack env
-
1
def headers=(new_headers)
-
@_response ||= ActionDispatch::Response.new
-
@_response.headers.replace(new_headers)
-
end
-
-
# Behavior specific to functional tests
-
1
module Functional # :nodoc:
-
1
def set_response!(request)
-
end
-
-
1
def recycle!
-
46
@_url_options = nil
-
46
self.formats = nil
-
46
self.params = nil
-
end
-
end
-
-
1
module ClassMethods
-
1
def before_filters
-
_process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name}
-
end
-
end
-
end
-
end
-
1
require 'rack/session/abstract/id'
-
1
require 'active_support/core_ext/object/to_query'
-
1
require 'active_support/core_ext/module/anonymous'
-
1
require 'active_support/core_ext/hash/keys'
-
1
require 'active_support/deprecation'
-
-
1
require 'rails-dom-testing'
-
-
1
module ActionController
-
1
module TemplateAssertions
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
4
setup :setup_subscriptions
-
4
teardown :teardown_subscriptions
-
end
-
-
1
RENDER_TEMPLATE_INSTANCE_VARIABLES = %w{partials templates layouts files}.freeze
-
-
1
def setup_subscriptions
-
7
RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
-
28
instance_variable_set("@_#{instance_variable}", Hash.new(0))
-
end
-
-
7
@_subscribers = []
-
-
@_subscribers << ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
-
9
path = payload[:layout]
-
9
if path
-
9
@_layouts[path] += 1
-
9
if path =~ /^layouts\/(.*)/
-
9
@_layouts[$1] += 1
-
end
-
end
-
7
end
-
-
@_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
-
18
if virtual_path = payload[:virtual_path]
-
18
partial = virtual_path =~ /^.*\/_[^\/]*$/
-
-
18
if partial
-
@_partials[virtual_path] += 1
-
@_partials[virtual_path.split("/").last] += 1
-
end
-
-
18
@_templates[virtual_path] += 1
-
else
-
path = payload[:identifier]
-
if path
-
@_files[path] += 1
-
@_files[path.split("/").last] += 1
-
end
-
end
-
7
end
-
end
-
-
1
def teardown_subscriptions
-
7
@_subscribers.each do |subscriber|
-
14
ActiveSupport::Notifications.unsubscribe(subscriber)
-
end
-
end
-
-
1
def process(*args)
-
23
reset_template_assertion
-
23
super
-
end
-
-
1
def reset_template_assertion
-
23
RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
-
92
ivar_name = "@_#{instance_variable}"
-
92
if instance_variable_defined?(ivar_name)
-
92
instance_variable_get(ivar_name).clear
-
end
-
end
-
end
-
-
# Asserts that the request was rendered with the appropriate template file or partials.
-
#
-
# # assert that the "new" view template was rendered
-
# assert_template "new"
-
#
-
# # assert that the exact template "admin/posts/new" was rendered
-
# assert_template %r{\Aadmin/posts/new\Z}
-
#
-
# # assert that the layout 'admin' was rendered
-
# assert_template layout: 'admin'
-
# assert_template layout: 'layouts/admin'
-
# assert_template layout: :admin
-
#
-
# # assert that no layout was rendered
-
# assert_template layout: nil
-
# assert_template layout: false
-
#
-
# # assert that the "_customer" partial was rendered twice
-
# assert_template partial: '_customer', count: 2
-
#
-
# # assert that no partials were rendered
-
# assert_template partial: false
-
#
-
# # assert that a file was rendered
-
# assert_template file: "README.rdoc"
-
#
-
# # assert that no file was rendered
-
# assert_template file: nil
-
# assert_template file: false
-
#
-
# In a view test case, you can also assert that specific locals are passed
-
# to partials:
-
#
-
# # assert that the "_customer" partial was rendered with a specific object
-
# assert_template partial: '_customer', locals: { customer: @customer }
-
1
def assert_template(options = {}, message = nil)
-
# Force body to be read in case the template is being streamed.
-
response.body
-
-
case options
-
when NilClass, Regexp, String, Symbol
-
options = options.to_s if Symbol === options
-
rendered = @_templates
-
msg = message || sprintf("expecting <%s> but rendering with <%s>",
-
options.inspect, rendered.keys)
-
matches_template =
-
case options
-
when String
-
!options.empty? && rendered.any? do |t, num|
-
options_splited = options.split(File::SEPARATOR)
-
t_splited = t.split(File::SEPARATOR)
-
t_splited.last(options_splited.size) == options_splited
-
end
-
when Regexp
-
rendered.any? { |t,num| t.match(options) }
-
when NilClass
-
rendered.blank?
-
end
-
assert matches_template, msg
-
when Hash
-
options.assert_valid_keys(:layout, :partial, :locals, :count, :file)
-
-
if options.key?(:layout)
-
expected_layout = options[:layout]
-
msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
-
expected_layout, @_layouts.keys)
-
-
case expected_layout
-
when String, Symbol
-
assert_includes @_layouts.keys, expected_layout.to_s, msg
-
when Regexp
-
assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg)
-
when nil, false
-
assert(@_layouts.empty?, msg)
-
end
-
end
-
-
if options[:file]
-
assert_includes @_files.keys, options[:file]
-
elsif options.key?(:file)
-
assert @_files.blank?, "expected no files but #{@_files.keys} was rendered"
-
end
-
-
if expected_partial = options[:partial]
-
if expected_locals = options[:locals]
-
if defined?(@_rendered_views)
-
view = expected_partial.to_s.sub(/^_/, '').sub(/\/_(?=[^\/]+\z)/, '/')
-
-
partial_was_not_rendered_msg = "expected %s to be rendered but it was not." % view
-
assert_includes @_rendered_views.rendered_views, view, partial_was_not_rendered_msg
-
-
msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial,
-
expected_locals,
-
@_rendered_views.locals_for(view)]
-
assert(@_rendered_views.view_rendered?(view, options[:locals]), msg)
-
else
-
warn "the :locals option to #assert_template is only supported in a ActionView::TestCase"
-
end
-
elsif expected_count = options[:count]
-
actual_count = @_partials[expected_partial]
-
msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)",
-
expected_partial, expected_count, actual_count)
-
assert(actual_count == expected_count.to_i, msg)
-
else
-
msg = message || sprintf("expecting partial <%s> but action rendered <%s>",
-
options[:partial], @_partials.keys)
-
assert_includes @_partials, expected_partial, msg
-
end
-
elsif options.key?(:partial)
-
assert @_partials.empty?,
-
"Expected no partials to be rendered"
-
end
-
else
-
raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil"
-
end
-
end
-
end
-
-
1
class TestRequest < ActionDispatch::TestRequest #:nodoc:
-
1
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
-
1
DEFAULT_ENV.delete 'PATH_INFO'
-
-
1
def initialize(env = {})
-
7
super
-
-
7
self.session = TestSession.new
-
7
self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => SecureRandom.hex(16))
-
end
-
-
1
def assign_parameters(routes, controller_path, action, parameters = {})
-
23
parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action)
-
23
extra_keys = routes.extra_keys(parameters)
-
23
non_path_parameters = get? ? query_parameters : request_parameters
-
23
parameters.each do |key, value|
-
76
if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?))
-
value = value.map{ |v| v.duplicable? ? v.dup : v }
-
elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? })
-
value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }]
-
elsif value.frozen? && value.duplicable?
-
value = value.dup
-
end
-
-
76
if extra_keys.include?(key)
-
18
non_path_parameters[key] = value
-
else
-
58
if value.is_a?(Array)
-
value = value.map(&:to_param)
-
else
-
58
value = value.to_param
-
end
-
-
58
path_parameters[key] = value
-
end
-
end
-
-
# Clear the combined params hash in case it was already referenced.
-
23
@env.delete("action_dispatch.request.parameters")
-
-
# Clear the filter cache variables so they're not stale
-
23
@filtered_parameters = @filtered_env = @filtered_path = nil
-
-
23
params = self.request_parameters.dup
-
23
%w(controller action only_path).each do |k|
-
69
params.delete(k)
-
69
params.delete(k.to_sym)
-
end
-
23
data = params.to_query
-
-
23
@env['CONTENT_LENGTH'] = data.length.to_s
-
23
@env['rack.input'] = StringIO.new(data)
-
end
-
-
1
def recycle!
-
23
@formats = nil
-
1034
@env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
-
906
@env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
-
23
@method = @request_method = nil
-
23
@fullpath = @ip = @remote_ip = @protocol = nil
-
23
@env['action_dispatch.request.query_parameters'] = {}
-
23
@set_cookies ||= {}
-
29
@set_cookies.update(Hash[cookie_jar.instance_variable_get("@set_cookies").map{ |k,o| [k,o[:value]] }])
-
23
deleted_cookies = cookie_jar.instance_variable_get("@delete_cookies")
-
37
@set_cookies.reject!{ |k,v| deleted_cookies.include?(k) }
-
23
cookie_jar.update(rack_cookies)
-
23
cookie_jar.update(cookies)
-
23
cookie_jar.update(@set_cookies)
-
23
cookie_jar.recycle!
-
end
-
-
1
private
-
-
1
def default_env
-
7
DEFAULT_ENV
-
end
-
end
-
-
1
class TestResponse < ActionDispatch::TestResponse
-
1
def recycle!
-
23
initialize
-
end
-
end
-
-
1
class LiveTestResponse < Live::Response
-
1
def recycle!
-
@body = nil
-
initialize
-
end
-
-
1
def body
-
@body ||= super
-
end
-
-
# Was the response successful?
-
1
alias_method :success?, :successful?
-
-
# Was the URL not found?
-
1
alias_method :missing?, :not_found?
-
-
# Were we redirected?
-
1
alias_method :redirect?, :redirection?
-
-
# Was there a server-side error?
-
1
alias_method :error?, :server_error?
-
end
-
-
# Methods #destroy and #load! are overridden to avoid calling methods on the
-
# @store object, which does not exist for the TestSession class.
-
1
class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
-
1
DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
-
-
1
def initialize(session = {})
-
7
super(nil, nil)
-
7
@id = SecureRandom.hex(16)
-
7
@data = stringify_keys(session)
-
7
@loaded = true
-
end
-
-
1
def exists?
-
true
-
end
-
-
1
def keys
-
@data.keys
-
end
-
-
1
def values
-
@data.values
-
end
-
-
1
def destroy
-
clear
-
end
-
-
1
private
-
-
1
def load!
-
@id
-
end
-
end
-
-
# Superclass for ActionController functional tests. Functional tests allow you to
-
# test a single controller action per test method. This should not be confused with
-
# integration tests (see ActionDispatch::IntegrationTest), which are more like
-
# "stories" that can involve multiple controllers and multiple actions (i.e. multiple
-
# different HTTP requests).
-
#
-
# == Basic example
-
#
-
# Functional tests are written as follows:
-
# 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate
-
# an HTTP request.
-
# 2. Then, one asserts whether the current state is as expected. "State" can be anything:
-
# the controller's HTTP response, the database contents, etc.
-
#
-
# For example:
-
#
-
# class BooksControllerTest < ActionController::TestCase
-
# def test_create
-
# # Simulate a POST response with the given HTTP parameters.
-
# post(:create, book: { title: "Love Hina" })
-
#
-
# # Assert that the controller tried to redirect us to
-
# # the created book's URI.
-
# assert_response :found
-
#
-
# # Assert that the controller really put the book in the database.
-
# assert_not_nil Book.find_by(title: "Love Hina")
-
# end
-
# end
-
#
-
# You can also send a real document in the simulated HTTP request.
-
#
-
# def test_create
-
# json = {book: { title: "Love Hina" }}.to_json
-
# post :create, json
-
# end
-
#
-
# == Special instance variables
-
#
-
# ActionController::TestCase will also automatically provide the following instance
-
# variables for use in the tests:
-
#
-
# <b>@controller</b>::
-
# The controller instance that will be tested.
-
# <b>@request</b>::
-
# An ActionController::TestRequest, representing the current HTTP
-
# request. You can modify this object before sending the HTTP request. For example,
-
# you might want to set some session properties before sending a GET request.
-
# <b>@response</b>::
-
# An ActionController::TestResponse object, representing the response
-
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
-
# after calling +post+. If the various assert methods are not sufficient, then you
-
# may use this object to inspect the HTTP response in detail.
-
#
-
# (Earlier versions of \Rails required each functional test to subclass
-
# Test::Unit::TestCase and define @controller, @request, @response in +setup+.)
-
#
-
# == Controller is automatically inferred
-
#
-
# ActionController::TestCase will automatically infer the controller under test
-
# from the test class name. If the controller cannot be inferred from the test
-
# class name, you can explicitly set it with +tests+.
-
#
-
# class SpecialEdgeCaseWidgetsControllerTest < ActionController::TestCase
-
# tests WidgetController
-
# end
-
#
-
# == \Testing controller internals
-
#
-
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
-
# can be used against. These collections are:
-
#
-
# * assigns: Instance variables assigned in the action that are available for the view.
-
# * session: Objects being saved in the session.
-
# * flash: The flash objects currently in the session.
-
# * cookies: \Cookies being sent to the user on this request.
-
#
-
# These collections can be used just like any other hash:
-
#
-
# assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
-
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
-
# assert flash.empty? # makes sure that there's nothing in the flash
-
#
-
# For historic reasons, the assigns hash uses string-based keys. So <tt>assigns[:person]</tt> won't work, but <tt>assigns["person"]</tt> will. To
-
# appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
-
# So <tt>assigns(:person)</tt> will work just like <tt>assigns["person"]</tt>, but again, <tt>assigns[:person]</tt> will not work.
-
#
-
# On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
-
#
-
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
-
# action call which can then be asserted against.
-
#
-
# == Manipulating session and cookie variables
-
#
-
# Sometimes you need to set up the session and cookie variables for a test.
-
# To do this just assign a value to the session or cookie collection:
-
#
-
# session[:key] = "value"
-
# cookies[:key] = "value"
-
#
-
# To clear the cookies for a test just clear the cookie collection:
-
#
-
# cookies.clear
-
#
-
# == \Testing named routes
-
#
-
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
-
#
-
# assert_redirected_to page_url(title: 'foo')
-
1
class TestCase < ActiveSupport::TestCase
-
1
module Behavior
-
1
extend ActiveSupport::Concern
-
1
include ActionDispatch::TestProcess
-
1
include ActiveSupport::Testing::ConstantLookup
-
1
include Rails::Dom::Testing::Assertions
-
-
1
attr_reader :response, :request
-
-
1
module ClassMethods
-
-
# Sets the controller class name. Useful if the name can't be inferred from test class.
-
# Normalizes +controller_class+ before using.
-
#
-
# tests WidgetController
-
# tests :widget
-
# tests 'widget'
-
1
def tests(controller_class)
-
case controller_class
-
when String, Symbol
-
self.controller_class = "#{controller_class.to_s.camelize}Controller".constantize
-
when Class
-
self.controller_class = controller_class
-
else
-
raise ArgumentError, "controller class must be a String, Symbol, or Class"
-
end
-
end
-
-
1
def controller_class=(new_class)
-
self._controller_class = new_class
-
end
-
-
1
def controller_class
-
if current_controller_class = self._controller_class
-
current_controller_class
-
else
-
self.controller_class = determine_default_controller_class(name)
-
end
-
end
-
-
1
def determine_default_controller_class(name)
-
determine_constant_from_test_name(name) do |constant|
-
Class === constant && constant < ActionController::Metal
-
end
-
end
-
end
-
-
# Simulate a GET request with the given parameters.
-
#
-
# - +action+: The controller action to call.
-
# - +parameters+: The HTTP parameters that you want to pass. This may
-
# be +nil+, a hash, or a string that is appropriately encoded
-
# (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
-
# - +session+: A hash of parameters to store in the session. This may be +nil+.
-
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
-
#
-
# You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with
-
# +post+, +patch+, +put+, +delete+, and +head+.
-
#
-
# Note that the request method is not verified. The different methods are
-
# available to make the tests more expressive.
-
1
def get(action, *args)
-
8
process(action, "GET", *args)
-
end
-
-
# Simulate a POST request with the given parameters and set/volley the response.
-
# See +get+ for more details.
-
1
def post(action, *args)
-
6
process(action, "POST", *args)
-
end
-
-
# Simulate a PATCH request with the given parameters and set/volley the response.
-
# See +get+ for more details.
-
1
def patch(action, *args)
-
process(action, "PATCH", *args)
-
end
-
-
# Simulate a PUT request with the given parameters and set/volley the response.
-
# See +get+ for more details.
-
1
def put(action, *args)
-
9
process(action, "PUT", *args)
-
end
-
-
# Simulate a DELETE request with the given parameters and set/volley the response.
-
# See +get+ for more details.
-
1
def delete(action, *args)
-
process(action, "DELETE", *args)
-
end
-
-
# Simulate a HEAD request with the given parameters and set/volley the response.
-
# See +get+ for more details.
-
1
def head(action, *args)
-
process(action, "HEAD", *args)
-
end
-
-
1
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
-
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
-
@request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
-
__send__(request_method, action, parameters, session, flash).tap do
-
@request.env.delete 'HTTP_X_REQUESTED_WITH'
-
@request.env.delete 'HTTP_ACCEPT'
-
end
-
end
-
1
alias xhr :xml_http_request
-
-
1
def paramify_values(hash_or_array_or_value)
-
53
case hash_or_array_or_value
-
when Hash
-
53
Hash[hash_or_array_or_value.map{|key, value| [key, paramify_values(value)] }]
-
when Array
-
hash_or_array_or_value.map {|i| paramify_values(i)}
-
when Rack::Test::UploadedFile, ActionDispatch::Http::UploadedFile
-
hash_or_array_or_value
-
else
-
30
hash_or_array_or_value.to_param
-
end
-
end
-
-
# Simulate a HTTP request to +action+ by specifying request method,
-
# parameters and set/volley the response.
-
#
-
# - +action+: The controller action to call.
-
# - +http_method+: Request method used to send the http request. Possible values
-
# are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+.
-
# - +parameters+: The HTTP parameters. This may be +nil+, a hash, or a
-
# string that is appropriately encoded (+application/x-www-form-urlencoded+
-
# or +multipart/form-data+).
-
# - +session+: A hash of parameters to store in the session. This may be +nil+.
-
# - +flash+: A hash of parameters to store in the flash. This may be +nil+.
-
#
-
# Example calling +create+ action and sending two params:
-
#
-
# process :create, 'POST', user: { name: 'Gaurish Sharma', email: 'user@example.com' }
-
#
-
# Example sending parameters, +nil+ session and setting a flash message:
-
#
-
# process :view, 'GET', { id: 7 }, nil, { notice: 'This is flash message' }
-
#
-
# To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests
-
# prefer using #get, #post, #patch, #put, #delete and #head methods
-
# respectively which will make tests more expressive.
-
#
-
# Note that the request method is not verified.
-
1
def process(action, http_method = 'GET', *args)
-
23
check_required_ivars
-
-
23
if args.first.is_a?(String) && http_method != 'HEAD'
-
@request.env['RAW_POST_DATA'] = args.shift
-
end
-
-
23
parameters, session, flash = args
-
23
parameters ||= {}
-
-
# Ensure that numbers and symbols passed as params are converted to
-
# proper params, as is the case when engaging rack.
-
23
parameters = paramify_values(parameters) if html_format?(parameters)
-
-
23
@html_document = nil
-
-
23
unless @controller.respond_to?(:recycle!)
-
7
@controller.extend(Testing::Functional)
-
end
-
-
23
@request.recycle!
-
23
@response.recycle!
-
23
@controller.recycle!
-
-
23
@request.env['REQUEST_METHOD'] = http_method
-
-
23
controller_class_name = @controller.class.anonymous? ?
-
"anonymous" :
-
@controller.class.controller_path
-
-
23
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
-
-
23
@request.session.update(session) if session
-
23
@request.flash.update(flash || {})
-
-
23
@controller.request = @request
-
23
@controller.response = @response
-
-
23
build_request_uri(action, parameters)
-
-
23
name = @request.parameters[:action]
-
-
23
@controller.recycle!
-
23
@controller.process(name)
-
-
23
if cookies = @request.env['action_dispatch.cookies']
-
23
unless @response.committed?
-
23
cookies.write(@response)
-
end
-
end
-
23
@response.prepare!
-
-
23
@assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
-
-
23
if flash_value = @request.flash.to_session_value
-
@request.session['flash'] = flash_value
-
end
-
-
23
@response
-
end
-
-
1
def setup_controller_request_and_response
-
7
@controller = nil unless defined? @controller
-
-
7
response_klass = TestResponse
-
-
7
if klass = self.class.controller_class
-
7
if klass < ActionController::Live
-
response_klass = LiveTestResponse
-
end
-
7
unless @controller
-
7
begin
-
7
@controller = klass.new
-
rescue
-
warn "could not construct controller #{klass}" if $VERBOSE
-
end
-
end
-
end
-
-
7
@request = build_request
-
7
@response = build_response response_klass
-
7
@response.request = @request
-
-
7
if @controller
-
7
@controller.request = @request
-
7
@controller.params = {}
-
end
-
end
-
-
1
def build_request
-
7
TestRequest.new
-
end
-
-
1
def build_response(klass)
-
7
klass.new
-
end
-
-
1
included do
-
2
include ActionController::TemplateAssertions
-
2
include ActionDispatch::Assertions
-
2
class_attribute :_controller_class
-
2
setup :setup_controller_request_and_response
-
end
-
-
1
private
-
-
1
def document_root_element
-
html_document.root
-
end
-
-
1
def check_required_ivars
-
# Sanity check for required instance variables so we can give an
-
# understandable error message.
-
23
[:@routes, :@controller, :@request, :@response].each do |iv_name|
-
92
if !instance_variable_defined?(iv_name) || instance_variable_get(iv_name).nil?
-
raise "#{iv_name} is nil: make sure you set it in your test's setup method."
-
end
-
end
-
end
-
-
1
def build_request_uri(action, parameters)
-
23
unless @request.env["PATH_INFO"]
-
7
options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters
-
7
options.update(
-
:action => action,
-
:relative_url_root => nil,
-
:_recall => @request.path_parameters)
-
-
7
if route_name = options.delete(:use_route)
-
ActiveSupport::Deprecation.warn <<-MSG.squish
-
Passing the `use_route` option in functional tests are deprecated.
-
Support for this option in the `process` method (and the related
-
`get`, `head`, `post`, `patch`, `put` and `delete` helpers) will
-
be removed in the next version without replacement.
-
-
Functional tests are essentially unit tests for controllers and
-
they should not require knowledge to how the application's routes
-
are configured. Instead, you should explicitly pass the appropiate
-
params to the `process` method.
-
-
Previously the engines guide also contained an incorrect example
-
that recommended using this option to test an engine's controllers
-
within the dummy application. That recommendation was incorrect
-
and has since been corrected. Instead, you should override the
-
`@routes` variable in the test case with `Foo::Engine.routes`. See
-
the updated engines guide for details.
-
MSG
-
end
-
-
7
url, query_string = @routes.path_for(options, route_name).split("?", 2)
-
-
7
@request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
-
7
@request.env["PATH_INFO"] = url
-
7
@request.env["QUERY_STRING"] = query_string || ""
-
end
-
end
-
-
1
def html_format?(parameters)
-
23
return true unless parameters.key?(:format)
-
Mime.fetch(parameters[:format]) { Mime['html'] }.html?
-
end
-
end
-
-
1
include Behavior
-
end
-
end
-
1
require 'rails-dom-testing'
-
-
1
module ActionDispatch
-
1
module Assertions
-
1
autoload :ResponseAssertions, 'action_dispatch/testing/assertions/response'
-
1
autoload :RoutingAssertions, 'action_dispatch/testing/assertions/routing'
-
-
1
extend ActiveSupport::Concern
-
-
1
include ResponseAssertions
-
1
include RoutingAssertions
-
1
include Rails::Dom::Testing::Assertions
-
-
1
def html_document
-
@html_document ||= if @response.content_type === Mime::XML
-
Nokogiri::XML::Document.parse(@response.body)
-
else
-
Nokogiri::HTML::Document.parse(@response.body)
-
end
-
end
-
end
-
end
-
-
1
module ActionDispatch
-
1
module Assertions
-
# A small suite of assertions that test responses from \Rails applications.
-
1
module ResponseAssertions
-
# Asserts that the response is one of the following types:
-
#
-
# * <tt>:success</tt> - Status code was in the 200-299 range
-
# * <tt>:redirect</tt> - Status code was in the 300-399 range
-
# * <tt>:missing</tt> - Status code was 404
-
# * <tt>:error</tt> - Status code was in the 500-599 range
-
#
-
# You can also pass an explicit status number like <tt>assert_response(501)</tt>
-
# or its symbolic equivalent <tt>assert_response(:not_implemented)</tt>.
-
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list.
-
#
-
# # assert that the response was a redirection
-
# assert_response :redirect
-
#
-
# # assert that the response code was status code 401 (unauthorized)
-
# assert_response 401
-
1
def assert_response(type, message = nil)
-
message ||= "Expected response to be a <#{type}>, but was <#{@response.response_code}>"
-
-
if Symbol === type
-
if [:success, :missing, :redirect, :error].include?(type)
-
assert @response.send("#{type}?"), message
-
else
-
code = Rack::Utils::SYMBOL_TO_STATUS_CODE[type]
-
if code.nil?
-
raise ArgumentError, "Invalid response type :#{type}"
-
end
-
assert_equal code, @response.response_code, message
-
end
-
else
-
assert_equal type, @response.response_code, message
-
end
-
end
-
-
# Assert that the redirection options passed in match those of the redirect called in the latest action.
-
# This match can be partial, such that <tt>assert_redirected_to(controller: "weblog")</tt> will also
-
# match the redirection of <tt>redirect_to(controller: "weblog", action: "show")</tt> and so on.
-
#
-
# # assert that the redirection was to the "index" action on the WeblogController
-
# assert_redirected_to controller: "weblog", action: "index"
-
#
-
# # assert that the redirection was to the named route login_url
-
# assert_redirected_to login_url
-
#
-
# # assert that the redirection was to the url for @customer
-
# assert_redirected_to @customer
-
#
-
# # asserts that the redirection matches the regular expression
-
# assert_redirected_to %r(\Ahttp://example.org)
-
1
def assert_redirected_to(options = {}, message=nil)
-
assert_response(:redirect, message)
-
return true if options === @response.location
-
-
redirect_is = normalize_argument_to_redirection(@response.location)
-
redirect_expected = normalize_argument_to_redirection(options)
-
-
message ||= "Expected response to be a redirect to <#{redirect_expected}> but was a redirect to <#{redirect_is}>"
-
assert_operator redirect_expected, :===, redirect_is, message
-
end
-
-
1
private
-
# Proxy to to_param if the object will respond to it.
-
1
def parameterize(value)
-
value.respond_to?(:to_param) ? value.to_param : value
-
end
-
-
1
def normalize_argument_to_redirection(fragment)
-
if Regexp === fragment
-
fragment
-
else
-
handle = @controller || ActionController::Redirecting
-
handle._compute_redirect_to_location(@request, fragment)
-
end
-
end
-
end
-
end
-
end
-
1
require 'uri'
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
1
require 'active_support/core_ext/string/access'
-
1
require 'action_controller/metal/exceptions'
-
-
1
module ActionDispatch
-
1
module Assertions
-
# Suite of assertions to test routes generated by \Rails and the handling of requests made to them.
-
1
module RoutingAssertions
-
# Asserts that the routing of the given +path+ was handled correctly and that the parsed options (given in the +expected_options+ hash)
-
# match +path+. Basically, it asserts that \Rails recognizes the route given by +expected_options+.
-
#
-
# Pass a hash in the second argument (+path+) to specify the request method. This is useful for routes
-
# requiring a specific HTTP method. The hash should contain a :path with the incoming request path
-
# and a :method containing the required HTTP verb.
-
#
-
# # assert that POSTing to /items will call the create action on ItemsController
-
# assert_recognizes({controller: 'items', action: 'create'}, {path: 'items', method: :post})
-
#
-
# You can also pass in +extras+ with a hash containing URL parameters that would normally be in the query string. This can be used
-
# to assert that values in the query string string will end up in the params hash correctly. To test query strings you must use the
-
# extras argument, appending the query string on the path directly will not work. For example:
-
#
-
# # assert that a path of '/items/list/1?view=print' returns the correct options
-
# assert_recognizes({controller: 'items', action: 'list', id: '1', view: 'print'}, 'items/list/1', { view: "print" })
-
#
-
# The +message+ parameter allows you to pass in an error message that is displayed upon failure.
-
#
-
# # Check the default route (i.e., the index action)
-
# assert_recognizes({controller: 'items', action: 'index'}, 'items')
-
#
-
# # Test a specific action
-
# assert_recognizes({controller: 'items', action: 'list'}, 'items/list')
-
#
-
# # Test an action with a parameter
-
# assert_recognizes({controller: 'items', action: 'destroy', id: '1'}, 'items/destroy/1')
-
#
-
# # Test a custom route
-
# assert_recognizes({controller: 'items', action: 'show', id: '1'}, 'view/item1')
-
1
def assert_recognizes(expected_options, path, extras={}, msg=nil)
-
if path.is_a?(Hash) && path[:method].to_s == "all"
-
[:get, :post, :put, :delete].each do |method|
-
assert_recognizes(expected_options, path.merge(method: method), extras, msg)
-
end
-
else
-
request = recognized_request_for(path, extras, msg)
-
-
expected_options = expected_options.clone
-
-
expected_options.stringify_keys!
-
-
msg = message(msg, "") {
-
sprintf("The recognized options <%s> did not match <%s>, difference:",
-
request.path_parameters, expected_options)
-
}
-
-
assert_equal(expected_options, request.path_parameters, msg)
-
end
-
end
-
-
# Asserts that the provided options can be used to generate the provided path. This is the inverse of +assert_recognizes+.
-
# The +extras+ parameter is used to tell the request the names and values of additional request parameters that would be in
-
# a query string. The +message+ parameter allows you to specify a custom error message for assertion failures.
-
#
-
# The +defaults+ parameter is unused.
-
#
-
# # Asserts that the default action is generated for a route with no action
-
# assert_generates "/items", controller: "items", action: "index"
-
#
-
# # Tests that the list action is properly routed
-
# assert_generates "/items/list", controller: "items", action: "list"
-
#
-
# # Tests the generation of a route with a parameter
-
# assert_generates "/items/list/1", { controller: "items", action: "list", id: "1" }
-
#
-
# # Asserts that the generated route gives us our custom route
-
# assert_generates "changesets/12", { controller: 'scm', action: 'show_diff', revision: "12" }
-
1
def assert_generates(expected_path, options, defaults={}, extras={}, message=nil)
-
if expected_path =~ %r{://}
-
fail_on(URI::InvalidURIError, message) do
-
uri = URI.parse(expected_path)
-
expected_path = uri.path.to_s.empty? ? "/" : uri.path
-
end
-
else
-
expected_path = "/#{expected_path}" unless expected_path.first == '/'
-
end
-
# Load routes.rb if it hasn't been loaded.
-
-
generated_path, extra_keys = @routes.generate_extras(options, defaults)
-
found_extras = options.reject { |k, _| ! extra_keys.include? k }
-
-
msg = message || sprintf("found extras <%s>, not <%s>", found_extras, extras)
-
assert_equal(extras, found_extras, msg)
-
-
msg = message || sprintf("The generated path <%s> did not match <%s>", generated_path,
-
expected_path)
-
assert_equal(expected_path, generated_path, msg)
-
end
-
-
# Asserts that path and options match both ways; in other words, it verifies that <tt>path</tt> generates
-
# <tt>options</tt> and then that <tt>options</tt> generates <tt>path</tt>. This essentially combines +assert_recognizes+
-
# and +assert_generates+ into one step.
-
#
-
# The +extras+ hash allows you to specify options that would normally be provided as a query string to the action. The
-
# +message+ parameter allows you to specify a custom error message to display upon failure.
-
#
-
# # Assert a basic route: a controller with the default action (index)
-
# assert_routing '/home', controller: 'home', action: 'index'
-
#
-
# # Test a route generated with a specific controller, action, and parameter (id)
-
# assert_routing '/entries/show/23', controller: 'entries', action: 'show', id: 23
-
#
-
# # Assert a basic route (controller + default action), with an error message if it fails
-
# assert_routing '/store', { controller: 'store', action: 'index' }, {}, {}, 'Route for store index not generated properly'
-
#
-
# # Tests a route, providing a defaults hash
-
# assert_routing 'controller/action/9', {id: "9", item: "square"}, {controller: "controller", action: "action"}, {}, {item: "square"}
-
#
-
# # Tests a route with a HTTP method
-
# assert_routing({ method: 'put', path: '/product/321' }, { controller: "product", action: "update", id: "321" })
-
1
def assert_routing(path, options, defaults={}, extras={}, message=nil)
-
assert_recognizes(options, path, extras, message)
-
-
controller, default_controller = options[:controller], defaults[:controller]
-
if controller && controller.include?(?/) && default_controller && default_controller.include?(?/)
-
options[:controller] = "/#{controller}"
-
end
-
-
generate_options = options.dup.delete_if{ |k, _| defaults.key?(k) }
-
assert_generates(path.is_a?(Hash) ? path[:path] : path, generate_options, defaults, extras, message)
-
end
-
-
# A helper to make it easier to test different route configurations.
-
# This method temporarily replaces @routes
-
# with a new RouteSet instance.
-
#
-
# The new instance is yielded to the passed block. Typically the block
-
# will create some routes using <tt>set.draw { match ... }</tt>:
-
#
-
# with_routing do |set|
-
# set.draw do
-
# resources :users
-
# end
-
# assert_equal "/users", users_path
-
# end
-
#
-
1
def with_routing
-
old_routes, @routes = @routes, ActionDispatch::Routing::RouteSet.new
-
if defined?(@controller) && @controller
-
old_controller, @controller = @controller, @controller.clone
-
_routes = @routes
-
-
@controller.singleton_class.send(:include, _routes.url_helpers)
-
@controller.view_context_class = Class.new(@controller.view_context_class) do
-
include _routes.url_helpers
-
end
-
end
-
yield @routes
-
ensure
-
@routes = old_routes
-
if defined?(@controller) && @controller
-
@controller = old_controller
-
end
-
end
-
-
# ROUTES TODO: These assertions should really work in an integration context
-
1
def method_missing(selector, *args, &block)
-
if defined?(@controller) && @controller && @routes && @routes.named_routes.route_defined?(selector)
-
@controller.send(selector, *args, &block)
-
else
-
super
-
end
-
end
-
-
1
private
-
# Recognizes the route for a given path.
-
1
def recognized_request_for(path, extras = {}, msg)
-
if path.is_a?(Hash)
-
method = path[:method]
-
path = path[:path]
-
else
-
method = :get
-
end
-
-
# Assume given controller
-
request = ActionController::TestRequest.new
-
-
if path =~ %r{://}
-
fail_on(URI::InvalidURIError, msg) do
-
uri = URI.parse(path)
-
request.env["rack.url_scheme"] = uri.scheme || "http"
-
request.host = uri.host if uri.host
-
request.port = uri.port if uri.port
-
request.path = uri.path.to_s.empty? ? "/" : uri.path
-
end
-
else
-
path = "/#{path}" unless path.first == "/"
-
request.path = path
-
end
-
-
request.request_method = method if method
-
-
params = fail_on(ActionController::RoutingError, msg) do
-
@routes.recognize_path(path, { :method => method, :extras => extras })
-
end
-
request.path_parameters = params.with_indifferent_access
-
-
request
-
end
-
-
1
def fail_on(exception_class, message)
-
yield
-
rescue exception_class => e
-
raise Minitest::Assertion, message || e.message
-
end
-
end
-
end
-
end
-
1
require 'stringio'
-
1
require 'uri'
-
1
require 'active_support/core_ext/kernel/singleton_class'
-
1
require 'active_support/core_ext/object/try'
-
1
require 'rack/test'
-
1
require 'minitest'
-
-
1
module ActionDispatch
-
1
module Integration #:nodoc:
-
1
module RequestHelpers
-
# Performs a GET request with the given parameters.
-
#
-
# - +path+: The URI (as a String) on which you want to perform a GET
-
# request.
-
# - +parameters+: The HTTP parameters that you want to pass. This may
-
# be +nil+,
-
# a Hash, or a String that is appropriately encoded
-
# (<tt>application/x-www-form-urlencoded</tt> or
-
# <tt>multipart/form-data</tt>).
-
# - +headers_or_env+: Additional headers to pass, as a Hash. The headers will be
-
# merged into the Rack env hash.
-
#
-
# This method returns a Response object, which one can use to
-
# inspect the details of the response. Furthermore, if this method was
-
# called from an ActionDispatch::IntegrationTest object, then that
-
# object's <tt>@response</tt> instance variable will point to the same
-
# response object.
-
#
-
# You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with
-
# +#post+, +#patch+, +#put+, +#delete+, and +#head+.
-
1
def get(path, parameters = nil, headers_or_env = nil)
-
process :get, path, parameters, headers_or_env
-
end
-
-
# Performs a POST request with the given parameters. See +#get+ for more
-
# details.
-
1
def post(path, parameters = nil, headers_or_env = nil)
-
process :post, path, parameters, headers_or_env
-
end
-
-
# Performs a PATCH request with the given parameters. See +#get+ for more
-
# details.
-
1
def patch(path, parameters = nil, headers_or_env = nil)
-
process :patch, path, parameters, headers_or_env
-
end
-
-
# Performs a PUT request with the given parameters. See +#get+ for more
-
# details.
-
1
def put(path, parameters = nil, headers_or_env = nil)
-
process :put, path, parameters, headers_or_env
-
end
-
-
# Performs a DELETE request with the given parameters. See +#get+ for
-
# more details.
-
1
def delete(path, parameters = nil, headers_or_env = nil)
-
process :delete, path, parameters, headers_or_env
-
end
-
-
# Performs a HEAD request with the given parameters. See +#get+ for more
-
# details.
-
1
def head(path, parameters = nil, headers_or_env = nil)
-
process :head, path, parameters, headers_or_env
-
end
-
-
# Performs an XMLHttpRequest request with the given parameters, mirroring
-
# a request from the Prototype library.
-
#
-
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
-
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
-
# string; the headers are a hash.
-
1
def xml_http_request(request_method, path, parameters = nil, headers_or_env = nil)
-
headers_or_env ||= {}
-
headers_or_env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
-
headers_or_env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
-
process(request_method, path, parameters, headers_or_env)
-
end
-
1
alias xhr :xml_http_request
-
-
# Follow a single redirect response. If the last response was not a
-
# redirect, an exception will be raised. Otherwise, the redirect is
-
# performed on the location header.
-
1
def follow_redirect!
-
raise "not a redirect! #{status} #{status_message}" unless redirect?
-
get(response.location)
-
status
-
end
-
-
# Performs a request using the specified method, following any subsequent
-
# redirect. Note that the redirects are followed until the response is
-
# not a redirect--this means you may run into an infinite loop if your
-
# redirect loops back to itself.
-
1
def request_via_redirect(http_method, path, parameters = nil, headers_or_env = nil)
-
process(http_method, path, parameters, headers_or_env)
-
follow_redirect! while redirect?
-
status
-
end
-
-
# Performs a GET request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def get_via_redirect(path, parameters = nil, headers_or_env = nil)
-
request_via_redirect(:get, path, parameters, headers_or_env)
-
end
-
-
# Performs a POST request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def post_via_redirect(path, parameters = nil, headers_or_env = nil)
-
request_via_redirect(:post, path, parameters, headers_or_env)
-
end
-
-
# Performs a PATCH request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def patch_via_redirect(path, parameters = nil, headers_or_env = nil)
-
request_via_redirect(:patch, path, parameters, headers_or_env)
-
end
-
-
# Performs a PUT request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def put_via_redirect(path, parameters = nil, headers_or_env = nil)
-
request_via_redirect(:put, path, parameters, headers_or_env)
-
end
-
-
# Performs a DELETE request, following any subsequent redirect.
-
# See +request_via_redirect+ for more information.
-
1
def delete_via_redirect(path, parameters = nil, headers_or_env = nil)
-
request_via_redirect(:delete, path, parameters, headers_or_env)
-
end
-
end
-
-
# An instance of this class represents a set of requests and responses
-
# performed sequentially by a test process. Because you can instantiate
-
# multiple sessions and run them side-by-side, you can also mimic (to some
-
# limited extent) multiple simultaneous users interacting with your system.
-
#
-
# Typically, you will instantiate a new session using
-
# IntegrationTest#open_session, rather than instantiating
-
# Integration::Session directly.
-
1
class Session
-
1
DEFAULT_HOST = "www.example.com"
-
-
1
include Minitest::Assertions
-
1
include TestProcess, RequestHelpers, Assertions
-
-
1
%w( status status_message headers body redirect? ).each do |method|
-
5
delegate method, :to => :response, :allow_nil => true
-
end
-
-
1
%w( path ).each do |method|
-
1
delegate method, :to => :request, :allow_nil => true
-
end
-
-
# The hostname used in the last request.
-
1
def host
-
@host || DEFAULT_HOST
-
end
-
1
attr_writer :host
-
-
# The remote_addr used in the last request.
-
1
attr_accessor :remote_addr
-
-
# The Accept header to send.
-
1
attr_accessor :accept
-
-
# A map of the cookies returned by the last response, and which will be
-
# sent with the next request.
-
1
def cookies
-
_mock_session.cookie_jar
-
end
-
-
# A reference to the controller instance used by the last request.
-
1
attr_reader :controller
-
-
# A reference to the request instance used by the last request.
-
1
attr_reader :request
-
-
# A reference to the response instance used by the last request.
-
1
attr_reader :response
-
-
# A running counter of the number of requests processed.
-
1
attr_accessor :request_count
-
-
1
include ActionDispatch::Routing::UrlFor
-
-
# Create and initialize a new Session instance.
-
1
def initialize(app)
-
super()
-
@app = app
-
-
# If the app is a Rails app, make url_helpers available on the session
-
# This makes app.url_for and app.foo_path available in the console
-
if app.respond_to?(:routes)
-
singleton_class.class_eval do
-
include app.routes.url_helpers
-
include app.routes.mounted_helpers
-
end
-
end
-
-
reset!
-
end
-
-
1
def url_options
-
@url_options ||= default_url_options.dup.tap do |url_options|
-
url_options.reverse_merge!(controller.url_options) if controller
-
-
if @app.respond_to?(:routes)
-
url_options.reverse_merge!(@app.routes.default_url_options)
-
end
-
-
url_options.reverse_merge!(:host => host, :protocol => https? ? "https" : "http")
-
end
-
end
-
-
# Resets the instance. This can be used to reset the state information
-
# in an existing session instance, so it can be used from a clean-slate
-
# condition.
-
#
-
# session.reset!
-
1
def reset!
-
@https = false
-
@controller = @request = @response = nil
-
@_mock_session = nil
-
@request_count = 0
-
@url_options = nil
-
-
self.host = DEFAULT_HOST
-
self.remote_addr = "127.0.0.1"
-
self.accept = "text/xml,application/xml,application/xhtml+xml," +
-
"text/html;q=0.9,text/plain;q=0.8,image/png," +
-
"*/*;q=0.5"
-
-
unless defined? @named_routes_configured
-
# the helpers are made protected by default--we make them public for
-
# easier access during testing and troubleshooting.
-
@named_routes_configured = true
-
end
-
end
-
-
# Specify whether or not the session should mimic a secure HTTPS request.
-
#
-
# session.https!
-
# session.https!(false)
-
1
def https!(flag = true)
-
@https = flag
-
end
-
-
# Returns +true+ if the session is mimicking a secure HTTPS request.
-
#
-
# if session.https?
-
# ...
-
# end
-
1
def https?
-
@https
-
end
-
-
# Set the host name to use in the next request.
-
#
-
# session.host! "www.example.com"
-
1
alias :host! :host=
-
-
1
private
-
1
def _mock_session
-
@_mock_session ||= Rack::MockSession.new(@app, host)
-
end
-
-
# Performs the actual request.
-
1
def process(method, path, parameters = nil, headers_or_env = nil)
-
if path =~ %r{://}
-
location = URI.parse(path)
-
https! URI::HTTPS === location if location.scheme
-
host! "#{location.host}:#{location.port}" if location.host
-
path = location.query ? "#{location.path}?#{location.query}" : location.path
-
end
-
-
hostname, port = host.split(':')
-
-
env = {
-
:method => method,
-
:params => parameters,
-
-
"SERVER_NAME" => hostname,
-
"SERVER_PORT" => port || (https? ? "443" : "80"),
-
"HTTPS" => https? ? "on" : "off",
-
"rack.url_scheme" => https? ? "https" : "http",
-
-
"REQUEST_URI" => path,
-
"HTTP_HOST" => host,
-
"REMOTE_ADDR" => remote_addr,
-
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
-
"HTTP_ACCEPT" => accept
-
}
-
# this modifies the passed env directly
-
Http::Headers.new(env).merge!(headers_or_env || {})
-
-
session = Rack::Test::Session.new(_mock_session)
-
-
# NOTE: rack-test v0.5 doesn't build a default uri correctly
-
# Make sure requested path is always a full uri
-
session.request(build_full_uri(path, env), env)
-
-
@request_count += 1
-
@request = ActionDispatch::Request.new(session.last_request.env)
-
response = _mock_session.last_response
-
@response = ActionDispatch::TestResponse.from_response(response)
-
@html_document = nil
-
@url_options = nil
-
-
@controller = session.last_request.env['action_controller.instance']
-
-
return response.status
-
end
-
-
1
def build_full_uri(path, env)
-
"#{env['rack.url_scheme']}://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{path}"
-
end
-
end
-
-
1
module Runner
-
1
include ActionDispatch::Assertions
-
-
1
def app
-
@app ||= nil
-
end
-
-
# Reset the current session. This is useful for testing multiple sessions
-
# in a single test case.
-
1
def reset!
-
@integration_session = Integration::Session.new(app)
-
end
-
-
1
def remove! # :nodoc:
-
@integration_session = nil
-
end
-
-
%w(get post patch put head delete cookies assigns
-
1
xml_http_request xhr get_via_redirect post_via_redirect).each do |method|
-
12
define_method(method) do |*args|
-
reset! unless integration_session
-
-
# reset the html_document variable, except for cookies/assigns calls
-
unless method == 'cookies' || method == 'assigns'
-
@html_document = nil
-
reset_template_assertion
-
end
-
-
integration_session.__send__(method, *args).tap do
-
copy_session_variables!
-
end
-
end
-
end
-
-
# Open a new session instance. If a block is given, the new session is
-
# yielded to the block before being returned.
-
#
-
# session = open_session do |sess|
-
# sess.extend(CustomAssertions)
-
# end
-
#
-
# By default, a single session is automatically created for you, but you
-
# can use this method to open multiple sessions that ought to be tested
-
# simultaneously.
-
1
def open_session
-
dup.tap do |session|
-
yield session if block_given?
-
end
-
end
-
-
# Copy the instance variables from the current session instance into the
-
# test instance.
-
1
def copy_session_variables! #:nodoc:
-
return unless integration_session
-
%w(controller response request).each do |var|
-
instance_variable_set("@#{var}", @integration_session.__send__(var))
-
end
-
end
-
-
1
def default_url_options
-
reset! unless integration_session
-
integration_session.default_url_options
-
end
-
-
1
def default_url_options=(options)
-
reset! unless integration_session
-
integration_session.default_url_options = options
-
end
-
-
1
def respond_to?(method, include_private = false)
-
integration_session.respond_to?(method, include_private) || super
-
end
-
-
# Delegate unhandled messages to the current session instance.
-
1
def method_missing(sym, *args, &block)
-
reset! unless integration_session
-
if integration_session.respond_to?(sym)
-
integration_session.__send__(sym, *args, &block).tap do
-
copy_session_variables!
-
end
-
else
-
super
-
end
-
end
-
-
1
private
-
1
def integration_session
-
@integration_session ||= nil
-
end
-
end
-
end
-
-
# An integration test spans multiple controllers and actions,
-
# tying them all together to ensure they work together as expected. It tests
-
# more completely than either unit or functional tests do, exercising the
-
# entire stack, from the dispatcher to the database.
-
#
-
# At its simplest, you simply extend <tt>IntegrationTest</tt> and write your tests
-
# using the get/post methods:
-
#
-
# require "test_helper"
-
#
-
# class ExampleTest < ActionDispatch::IntegrationTest
-
# fixtures :people
-
#
-
# def test_login
-
# # get the login page
-
# get "/login"
-
# assert_equal 200, status
-
#
-
# # post the login and follow through to the home page
-
# post "/login", username: people(:jamis).username,
-
# password: people(:jamis).password
-
# follow_redirect!
-
# assert_equal 200, status
-
# assert_equal "/home", path
-
# end
-
# end
-
#
-
# However, you can also have multiple session instances open per test, and
-
# even extend those instances with assertions and methods to create a very
-
# powerful testing DSL that is specific for your application. You can even
-
# reference any named routes you happen to have defined.
-
#
-
# require "test_helper"
-
#
-
# class AdvancedTest < ActionDispatch::IntegrationTest
-
# fixtures :people, :rooms
-
#
-
# def test_login_and_speak
-
# jamis, david = login(:jamis), login(:david)
-
# room = rooms(:office)
-
#
-
# jamis.enter(room)
-
# jamis.speak(room, "anybody home?")
-
#
-
# david.enter(room)
-
# david.speak(room, "hello!")
-
# end
-
#
-
# private
-
#
-
# module CustomAssertions
-
# def enter(room)
-
# # reference a named route, for maximum internal consistency!
-
# get(room_url(id: room.id))
-
# assert(...)
-
# ...
-
# end
-
#
-
# def speak(room, message)
-
# xml_http_request "/say/#{room.id}", message: message
-
# assert(...)
-
# ...
-
# end
-
# end
-
#
-
# def login(who)
-
# open_session do |sess|
-
# sess.extend(CustomAssertions)
-
# who = people(who)
-
# sess.post "/login", username: who.username,
-
# password: who.password
-
# assert(...)
-
# end
-
# end
-
# end
-
1
class IntegrationTest < ActiveSupport::TestCase
-
1
include Integration::Runner
-
1
include ActionController::TemplateAssertions
-
1
include ActionDispatch::Routing::UrlFor
-
-
1
@@app = nil
-
-
1
def self.app
-
@@app || ActionDispatch.test_app
-
end
-
-
1
def self.app=(app)
-
@@app = app
-
end
-
-
1
def app
-
super || self.class.app
-
end
-
-
1
def url_options
-
reset! unless integration_session
-
integration_session.url_options
-
end
-
-
1
def document_root_element
-
html_document.root
-
end
-
end
-
end
-
1
require 'action_dispatch/middleware/cookies'
-
1
require 'action_dispatch/middleware/flash'
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
-
1
module ActionDispatch
-
1
module TestProcess
-
1
def assigns(key = nil)
-
assigns = {}.with_indifferent_access
-
@controller.view_assigns.each { |k, v| assigns.regular_writer(k, v) }
-
key.nil? ? assigns : assigns[key]
-
end
-
-
1
def session
-
@request.session
-
end
-
-
1
def flash
-
@request.flash
-
end
-
-
1
def cookies
-
@request.cookie_jar
-
end
-
-
1
def redirect_to_url
-
@response.redirect_url
-
end
-
-
# Shortcut for <tt>Rack::Test::UploadedFile.new(File.join(ActionController::TestCase.fixture_path, path), type)</tt>:
-
#
-
# post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png')
-
#
-
# To upload binary files on Windows, pass <tt>:binary</tt> as the last parameter.
-
# This will not affect other platforms:
-
#
-
# post :change_avatar, avatar: fixture_file_upload('files/spongebob.png', 'image/png', :binary)
-
1
def fixture_file_upload(path, mime_type = nil, binary = false)
-
if self.class.respond_to?(:fixture_path) && self.class.fixture_path
-
path = File.join(self.class.fixture_path, path)
-
end
-
Rack::Test::UploadedFile.new(path, mime_type, binary)
-
end
-
end
-
end
-
1
require 'active_support/core_ext/hash/indifferent_access'
-
1
require 'rack/utils'
-
-
1
module ActionDispatch
-
1
class TestRequest < Request
-
1
DEFAULT_ENV = Rack::MockRequest.env_for('/',
-
'HTTP_HOST' => 'test.host',
-
'REMOTE_ADDR' => '0.0.0.0',
-
'HTTP_USER_AGENT' => 'Rails Testing'
-
)
-
-
1
def self.new(env = {})
-
7
super
-
end
-
-
1
def initialize(env = {})
-
7
env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
-
7
super(default_env.merge(env))
-
end
-
-
1
def request_method=(method)
-
@env['REQUEST_METHOD'] = method.to_s.upcase
-
end
-
-
1
def host=(host)
-
@env['HTTP_HOST'] = host
-
end
-
-
1
def port=(number)
-
@env['SERVER_PORT'] = number.to_i
-
end
-
-
1
def request_uri=(uri)
-
@env['REQUEST_URI'] = uri
-
end
-
-
1
def path=(path)
-
@env['PATH_INFO'] = path
-
end
-
-
1
def action=(action_name)
-
path_parameters[:action] = action_name.to_s
-
end
-
-
1
def if_modified_since=(last_modified)
-
@env['HTTP_IF_MODIFIED_SINCE'] = last_modified
-
end
-
-
1
def if_none_match=(etag)
-
@env['HTTP_IF_NONE_MATCH'] = etag
-
end
-
-
1
def remote_addr=(addr)
-
@env['REMOTE_ADDR'] = addr
-
end
-
-
1
def user_agent=(user_agent)
-
@env['HTTP_USER_AGENT'] = user_agent
-
end
-
-
1
def accept=(mime_types)
-
@env.delete('action_dispatch.request.accepts')
-
@env['HTTP_ACCEPT'] = Array(mime_types).collect { |mime_type| mime_type.to_s }.join(",")
-
end
-
-
1
alias :rack_cookies :cookies
-
-
1
def cookies
-
30
@cookies ||= {}.with_indifferent_access
-
end
-
-
1
private
-
-
1
def default_env
-
DEFAULT_ENV
-
end
-
end
-
end
-
1
module ActionDispatch
-
# Integration test methods such as ActionDispatch::Integration::Session#get
-
# and ActionDispatch::Integration::Session#post return objects of class
-
# TestResponse, which represent the HTTP response results of the requested
-
# controller actions.
-
#
-
# See Response for more information on controller response objects.
-
1
class TestResponse < Response
-
1
def self.from_response(response)
-
new response.status, response.headers, response.body, default_headers: nil
-
end
-
-
# Was the response successful?
-
1
alias_method :success?, :successful?
-
-
# Was the URL not found?
-
1
alias_method :missing?, :not_found?
-
-
# Were we redirected?
-
1
alias_method :redirect?, :redirection?
-
-
# Was there a server-side error?
-
1
alias_method :error?, :server_error?
-
end
-
end
-
1
require 'active_support/core_ext/string/output_safety'
-
-
1
module ActionView
-
1
class OutputFlow #:nodoc:
-
1
attr_reader :content
-
-
1
def initialize
-
9
@content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
-
end
-
-
# Called by _layout_for to read stored values.
-
1
def get(key)
-
@content[key]
-
end
-
-
# Called by each renderer object to set the layout contents.
-
1
def set(key, value)
-
9
@content[key] = value
-
end
-
-
# Called by content_for
-
1
def append(key, value)
-
@content[key] << value
-
end
-
1
alias_method :append!, :append
-
-
end
-
-
1
class StreamingFlow < OutputFlow #:nodoc:
-
1
def initialize(view, fiber)
-
@view = view
-
@parent = nil
-
@child = view.output_buffer
-
@content = view.view_flow.content
-
@fiber = fiber
-
@root = Fiber.current.object_id
-
end
-
-
# Try to get stored content. If the content
-
# is not available and we are inside the layout
-
# fiber, we set that we are waiting for the given
-
# key and yield.
-
1
def get(key)
-
return super if @content.key?(key)
-
-
if inside_fiber?
-
view = @view
-
-
begin
-
@waiting_for = key
-
view.output_buffer, @parent = @child, view.output_buffer
-
Fiber.yield
-
ensure
-
@waiting_for = nil
-
view.output_buffer, @child = @parent, view.output_buffer
-
end
-
end
-
-
super
-
end
-
-
# Appends the contents for the given key. This is called
-
# by provides and resumes back to the fiber if it is
-
# the key it is waiting for.
-
1
def append!(key, value)
-
super
-
@fiber.resume if @waiting_for == key
-
end
-
-
1
private
-
-
1
def inside_fiber?
-
Fiber.current.object_id != @root
-
end
-
end
-
end
-
1
module ActionView
-
# This class defines the interface for a renderer. Each class that
-
# subclasses +AbstractRenderer+ is used by the base +Renderer+ class to
-
# render a specific type of object.
-
#
-
# The base +Renderer+ class uses its +render+ method to delegate to the
-
# renderers. These currently consist of
-
#
-
# PartialRenderer - Used for rendering partials
-
# TemplateRenderer - Used for rendering other types of templates
-
# StreamingTemplateRenderer - Used for streaming
-
#
-
# Whenever the +render+ method is called on the base +Renderer+ class, a new
-
# renderer object of the correct type is created, and the +render+ method on
-
# that new object is called in turn. This abstracts the setup and rendering
-
# into a separate classes for partials and templates.
-
1
class AbstractRenderer #:nodoc:
-
1
delegate :find_template, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
-
-
1
def initialize(lookup_context)
-
9
@lookup_context = lookup_context
-
end
-
-
1
def render
-
raise NotImplementedError
-
end
-
-
1
protected
-
-
1
def extract_details(options)
-
9
@lookup_context.registered_details.each_with_object({}) do |key, details|
-
36
value = options[key]
-
-
36
details[key] = Array(value) if value
-
end
-
end
-
-
1
def instrument(name, options={})
-
18
ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield }
-
end
-
-
1
def prepend_formats(formats)
-
9
formats = Array(formats)
-
9
return if formats.empty? || @lookup_context.html_fallback_for_js
-
-
9
@lookup_context.formats = formats | @lookup_context.formats
-
end
-
end
-
end
-
1
module ActionView
-
# This is the main entry point for rendering. It basically delegates
-
# to other objects like TemplateRenderer and PartialRenderer which
-
# actually renders the template.
-
#
-
# The Renderer will parse the options from the +render+ or +render_body+
-
# method and render a partial or a template based on the options. The
-
# +TemplateRenderer+ and +PartialRenderer+ objects are wrappers which do all
-
# the setup and logic necessary to render a view and a new object is created
-
# each time +render+ is called.
-
1
class Renderer
-
1
attr_accessor :lookup_context
-
-
1
def initialize(lookup_context)
-
5
@lookup_context = lookup_context
-
end
-
-
# Main render entry point shared by AV and AC.
-
1
def render(context, options)
-
9
if options.key?(:partial)
-
render_partial(context, options)
-
else
-
9
render_template(context, options)
-
end
-
end
-
-
# Render but returns a valid Rack body. If fibers are defined, we return
-
# a streaming body that renders the template piece by piece.
-
#
-
# Note that partials are not supported to be rendered with streaming,
-
# so in such cases, we just wrap them in an array.
-
1
def render_body(context, options)
-
if options.key?(:partial)
-
[render_partial(context, options)]
-
else
-
StreamingTemplateRenderer.new(@lookup_context).render(context, options)
-
end
-
end
-
-
# Direct accessor to template rendering.
-
1
def render_template(context, options) #:nodoc:
-
9
TemplateRenderer.new(@lookup_context).render(context, options)
-
end
-
-
# Direct access to partial rendering.
-
1
def render_partial(context, options, &block) #:nodoc:
-
PartialRenderer.new(@lookup_context).render(context, options, block)
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/try'
-
-
1
module ActionView
-
1
class TemplateRenderer < AbstractRenderer #:nodoc:
-
1
def render(context, options)
-
9
@view = context
-
9
@details = extract_details(options)
-
9
template = determine_template(options)
-
-
9
prepend_formats(template.formats)
-
-
9
@lookup_context.rendered_format ||= (template.formats.first || formats.first)
-
-
9
render_template(template, options[:layout], options[:locals])
-
end
-
-
1
private
-
-
# Determine the template to be rendered using the given options.
-
1
def determine_template(options)
-
9
keys = options.has_key?(:locals) ? options[:locals].keys : []
-
-
9
if options.key?(:body)
-
Template::Text.new(options[:body])
-
9
elsif options.key?(:text)
-
Template::Text.new(options[:text], formats.first)
-
9
elsif options.key?(:plain)
-
Template::Text.new(options[:plain])
-
9
elsif options.key?(:html)
-
Template::HTML.new(options[:html], formats.first)
-
9
elsif options.key?(:file)
-
with_fallbacks { find_template(options[:file], nil, false, keys, @details) }
-
9
elsif options.key?(:inline)
-
handler = Template.handler_for_extension(options[:type] || "erb")
-
Template.new(options[:inline], "inline template", handler, :locals => keys)
-
9
elsif options.key?(:template)
-
9
if options[:template].respond_to?(:render)
-
options[:template]
-
else
-
9
find_template(options[:template], options[:prefixes], false, keys, @details)
-
end
-
else
-
raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :text or :body option."
-
end
-
end
-
-
# Renders the given template. A string representing the layout can be
-
# supplied as well.
-
1
def render_template(template, layout_name = nil, locals = nil) #:nodoc:
-
9
view, locals = @view, locals || {}
-
-
9
render_with_layout(layout_name, locals) do |layout|
-
9
instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
-
9
template.render(view, locals) { |*name| view._layout_for(*name) }
-
end
-
end
-
end
-
-
1
def render_with_layout(path, locals) #:nodoc:
-
9
layout = path && find_layout(path, locals.keys)
-
9
content = yield(layout)
-
-
9
if layout
-
9
view = @view
-
9
view.view_flow.set(:layout, content)
-
9
layout.render(view, locals){ |*name| view._layout_for(*name) }
-
else
-
content
-
end
-
end
-
-
# This is the method which actually finds the layout using details in the lookup
-
# context object. If no layout is found, it checks if at least a layout with
-
# the given name exists across all details before raising the error.
-
1
def find_layout(layout, keys)
-
18
with_layout_format { resolve_layout(layout, keys) }
-
end
-
-
1
def resolve_layout(layout, keys)
-
18
case layout
-
when String
-
begin
-
if layout =~ /^\//
-
with_fallbacks { find_template(layout, nil, false, keys, @details) }
-
else
-
find_template(layout, nil, false, keys, @details)
-
end
-
rescue ActionView::MissingTemplate
-
all_details = @details.merge(:formats => @lookup_context.default_formats)
-
raise unless template_exists?(layout, nil, false, keys, all_details)
-
end
-
when Proc
-
9
resolve_layout(layout.call, keys)
-
when FalseClass
-
nil
-
else
-
9
layout
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/module/remove_method'
-
1
require 'action_controller'
-
1
require 'action_controller/test_case'
-
1
require 'action_view'
-
-
1
require 'rails-dom-testing'
-
-
1
module ActionView
-
# = Action View Test Case
-
1
class TestCase < ActiveSupport::TestCase
-
1
class TestController < ActionController::Base
-
1
include ActionDispatch::TestProcess
-
-
1
attr_accessor :request, :response, :params
-
-
1
class << self
-
1
attr_writer :controller_path
-
end
-
-
1
def controller_path=(path)
-
self.class.controller_path=(path)
-
end
-
-
1
def initialize
-
super
-
self.class.controller_path = ""
-
@request = ActionController::TestRequest.new
-
@response = ActionController::TestResponse.new
-
-
@request.env.delete('PATH_INFO')
-
@params = {}
-
end
-
end
-
-
1
module Behavior
-
1
extend ActiveSupport::Concern
-
-
1
include ActionDispatch::Assertions, ActionDispatch::TestProcess
-
1
include Rails::Dom::Testing::Assertions
-
1
include ActionController::TemplateAssertions
-
1
include ActionView::Context
-
-
1
include ActionDispatch::Routing::PolymorphicRoutes
-
-
1
include AbstractController::Helpers
-
1
include ActionView::Helpers
-
1
include ActionView::RecordIdentifier
-
1
include ActionView::RoutingUrlFor
-
-
1
include ActiveSupport::Testing::ConstantLookup
-
-
1
delegate :lookup_context, :to => :controller
-
1
attr_accessor :controller, :output_buffer, :rendered
-
-
1
module ClassMethods
-
1
def tests(helper_class)
-
case helper_class
-
when String, Symbol
-
self.helper_class = "#{helper_class.to_s.underscore}_helper".camelize.safe_constantize
-
when Module
-
self.helper_class = helper_class
-
end
-
end
-
-
1
def determine_default_helper_class(name)
-
determine_constant_from_test_name(name) do |constant|
-
Module === constant && !(Class === constant)
-
end
-
end
-
-
1
def helper_method(*methods)
-
# Almost a duplicate from ActionController::Helpers
-
methods.flatten.each do |method|
-
_helpers.module_eval <<-end_eval
-
def #{method}(*args, &block) # def current_user(*args, &block)
-
_test_case.send(%(#{method}), *args, &block) # _test_case.send(%(current_user), *args, &block)
-
end # end
-
end_eval
-
end
-
end
-
-
1
attr_writer :helper_class
-
-
1
def helper_class
-
@helper_class ||= determine_default_helper_class(name)
-
end
-
-
1
def new(*)
-
include_helper_modules!
-
super
-
end
-
-
1
private
-
-
1
def include_helper_modules!
-
helper(helper_class) if helper_class
-
include _helpers
-
end
-
-
end
-
-
1
def setup_with_controller
-
@controller = ActionView::TestCase::TestController.new
-
@request = @controller.request
-
# empty string ensures buffer has UTF-8 encoding as
-
# new without arguments returns ASCII-8BIT encoded buffer like String#new
-
@output_buffer = ActiveSupport::SafeBuffer.new ''
-
@rendered = ''
-
-
make_test_case_available_to_view!
-
say_no_to_protect_against_forgery!
-
end
-
-
1
def config
-
@controller.config if @controller.respond_to?(:config)
-
end
-
-
1
def render(options = {}, local_assigns = {}, &block)
-
view.assign(view_assigns)
-
@rendered << output = view.render(options, local_assigns, &block)
-
output
-
end
-
-
1
def rendered_views
-
@_rendered_views ||= RenderedViewsCollection.new
-
end
-
-
# Need to experiment if this priority is the best one: rendered => output_buffer
-
1
class RenderedViewsCollection
-
1
def initialize
-
@rendered_views ||= Hash.new { |hash, key| hash[key] = [] }
-
end
-
-
1
def add(view, locals)
-
@rendered_views[view] ||= []
-
@rendered_views[view] << locals
-
end
-
-
1
def locals_for(view)
-
@rendered_views[view]
-
end
-
-
1
def rendered_views
-
@rendered_views.keys
-
end
-
-
1
def view_rendered?(view, expected_locals)
-
locals_for(view).any? do |actual_locals|
-
expected_locals.all? {|key, value| value == actual_locals[key] }
-
end
-
end
-
end
-
-
1
included do
-
1
setup :setup_with_controller
-
end
-
-
1
private
-
-
# Need to experiment if this priority is the best one: rendered => output_buffer
-
1
def document_root_element
-
Nokogiri::HTML::Document.parse(@rendered.blank? ? @output_buffer : @rendered).root
-
end
-
-
1
def say_no_to_protect_against_forgery!
-
_helpers.module_eval do
-
remove_possible_method :protect_against_forgery?
-
def protect_against_forgery?
-
false
-
end
-
end
-
end
-
-
1
def make_test_case_available_to_view!
-
test_case_instance = self
-
_helpers.module_eval do
-
unless private_method_defined?(:_test_case)
-
define_method(:_test_case) { test_case_instance }
-
private :_test_case
-
end
-
end
-
end
-
-
1
module Locals
-
1
attr_accessor :rendered_views
-
-
1
def render(options = {}, local_assigns = {})
-
case options
-
when Hash
-
if block_given?
-
rendered_views.add options[:layout], options[:locals]
-
elsif options.key?(:partial)
-
rendered_views.add options[:partial], options[:locals]
-
end
-
else
-
rendered_views.add options, local_assigns
-
end
-
-
super
-
end
-
end
-
-
# The instance of ActionView::Base that is used by +render+.
-
1
def view
-
@view ||= begin
-
view = @controller.view_context
-
view.singleton_class.send :include, _helpers
-
view.extend(Locals)
-
view.rendered_views = self.rendered_views
-
view.output_buffer = self.output_buffer
-
view
-
end
-
end
-
-
1
alias_method :_view, :view
-
-
1
INTERNAL_IVARS = [
-
:@NAME,
-
:@failures,
-
:@assertions,
-
:@__io__,
-
:@_assertion_wrapped,
-
:@_assertions,
-
:@_result,
-
:@_routes,
-
:@controller,
-
:@_layouts,
-
:@_files,
-
:@_rendered_views,
-
:@method_name,
-
:@output_buffer,
-
:@_partials,
-
:@passed,
-
:@rendered,
-
:@request,
-
:@routes,
-
:@tagged_logger,
-
:@_templates,
-
:@options,
-
:@test_passed,
-
:@view,
-
:@view_context_class,
-
:@_subscribers,
-
:@html_document
-
]
-
-
1
def _user_defined_ivars
-
instance_variables - INTERNAL_IVARS
-
end
-
-
# Returns a Hash of instance variables and their values, as defined by
-
# the user in the test case, which are then assigned to the view being
-
# rendered. This is generally intended for internal use and extension
-
# frameworks.
-
1
def view_assigns
-
Hash[_user_defined_ivars.map do |ivar|
-
[ivar[1..-1].to_sym, instance_variable_get(ivar)]
-
end]
-
end
-
-
1
def _routes
-
@controller._routes if @controller.respond_to?(:_routes)
-
end
-
-
1
def method_missing(selector, *args)
-
if @controller.respond_to?(:_routes) &&
-
( @controller._routes.named_routes.route_defined?(selector) ||
-
@controller._routes.mounted_helpers.method_defined?(selector) )
-
@controller.__send__(selector, *args)
-
else
-
super
-
end
-
end
-
end
-
-
1
include Behavior
-
end
-
end
-
1
require 'action_view/template/resolver'
-
-
1
module ActionView #:nodoc:
-
# Use FixtureResolver in your tests to simulate the presence of files on the
-
# file system. This is used internally by Rails' own test suite, and is
-
# useful for testing extensions that have no way of knowing what the file
-
# system will look like at runtime.
-
1
class FixtureResolver < PathResolver
-
1
attr_reader :hash
-
-
1
def initialize(hash = {}, pattern=nil)
-
super(pattern)
-
@hash = hash
-
end
-
-
1
def to_s
-
@hash.keys.join(', ')
-
end
-
-
1
private
-
-
1
def query(path, exts, formats)
-
query = ""
-
EXTENSIONS.each_key do |ext|
-
query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'
-
end
-
query = /^(#{Regexp.escape(path)})#{query}$/
-
-
templates = []
-
@hash.each do |_path, array|
-
source, updated_at = array
-
next unless _path =~ query
-
handler, format, variant = extract_handler_and_format_and_variant(_path, formats)
-
templates << Template.new(source, _path, handler,
-
:virtual_path => path.virtual,
-
:format => format,
-
:variant => variant,
-
:updated_at => updated_at
-
)
-
end
-
-
templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
-
end
-
end
-
-
1
class NullResolver < PathResolver
-
1
def query(path, exts, formats)
-
handler, format, variant = extract_handler_and_format_and_variant(path, formats)
-
[ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format, :variant => variant)]
-
end
-
end
-
-
end
-
-
# -*- coding: utf-8 -*-
-
-
1
require 'active_support/core_ext/array/conversions'
-
1
require 'active_support/core_ext/string/inflections'
-
-
1
module ActiveModel
-
# == Active \Model \Errors
-
#
-
# Provides a modified +Hash+ that you can include in your object
-
# for handling error messages and interacting with Action View helpers.
-
#
-
# A minimal implementation could be:
-
#
-
# class Person
-
# # Required dependency for ActiveModel::Errors
-
# extend ActiveModel::Naming
-
#
-
# def initialize
-
# @errors = ActiveModel::Errors.new(self)
-
# end
-
#
-
# attr_accessor :name
-
# attr_reader :errors
-
#
-
# def validate!
-
# errors.add(:name, "cannot be nil") if name.nil?
-
# end
-
#
-
# # The following methods are needed to be minimally implemented
-
#
-
# def read_attribute_for_validation(attr)
-
# send(attr)
-
# end
-
#
-
# def Person.human_attribute_name(attr, options = {})
-
# attr
-
# end
-
#
-
# def Person.lookup_ancestors
-
# [self]
-
# end
-
# end
-
#
-
# The last three methods are required in your object for Errors to be
-
# able to generate error messages correctly and also handle multiple
-
# languages. Of course, if you extend your object with ActiveModel::Translation
-
# you will not need to implement the last two. Likewise, using
-
# ActiveModel::Validations will handle the validation related methods
-
# for you.
-
#
-
# The above allows you to do:
-
#
-
# person = Person.new
-
# person.validate! # => ["cannot be nil"]
-
# person.errors.full_messages # => ["name cannot be nil"]
-
# # etc..
-
1
class Errors
-
1
include Enumerable
-
-
1
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
-
-
1
attr_reader :messages
-
-
# Pass in the instance of the object that is using the errors object.
-
#
-
# class Person
-
# def initialize
-
# @errors = ActiveModel::Errors.new(self)
-
# end
-
# end
-
1
def initialize(base)
-
55
@base = base
-
55
@messages = {}
-
end
-
-
1
def initialize_dup(other) # :nodoc:
-
@messages = other.messages.dup
-
super
-
end
-
-
# Clear the error messages.
-
#
-
# person.errors.full_messages # => ["name cannot be nil"]
-
# person.errors.clear
-
# person.errors.full_messages # => []
-
1
def clear
-
55
messages.clear
-
end
-
-
# Returns +true+ if the error messages include an error for the given key
-
# +attribute+, +false+ otherwise.
-
#
-
# person.errors.messages # => {:name=>["cannot be nil"]}
-
# person.errors.include?(:name) # => true
-
# person.errors.include?(:age) # => false
-
1
def include?(attribute)
-
messages[attribute].present?
-
end
-
# aliases include?
-
1
alias :has_key? :include?
-
# aliases include?
-
1
alias :key? :include?
-
-
# Get messages for +key+.
-
#
-
# person.errors.messages # => {:name=>["cannot be nil"]}
-
# person.errors.get(:name) # => ["cannot be nil"]
-
# person.errors.get(:age) # => nil
-
1
def get(key)
-
messages[key]
-
end
-
-
# Set messages for +key+ to +value+.
-
#
-
# person.errors.get(:name) # => ["cannot be nil"]
-
# person.errors.set(:name, ["can't be nil"])
-
# person.errors.get(:name) # => ["can't be nil"]
-
1
def set(key, value)
-
messages[key] = value
-
end
-
-
# Delete messages for +key+. Returns the deleted messages.
-
#
-
# person.errors.get(:name) # => ["cannot be nil"]
-
# person.errors.delete(:name) # => ["cannot be nil"]
-
# person.errors.get(:name) # => nil
-
1
def delete(key)
-
messages.delete(key)
-
end
-
-
# When passed a symbol or a name of a method, returns an array of errors
-
# for the method.
-
#
-
# person.errors[:name] # => ["cannot be nil"]
-
# person.errors['name'] # => ["cannot be nil"]
-
1
def [](attribute)
-
get(attribute.to_sym) || set(attribute.to_sym, [])
-
end
-
-
# Adds to the supplied attribute the supplied error message.
-
#
-
# person.errors[:name] = "must be set"
-
# person.errors[:name] # => ['must be set']
-
1
def []=(attribute, error)
-
self[attribute] << error
-
end
-
-
# Iterates through each error key, value pair in the error messages hash.
-
# Yields the attribute and the error for that attribute. If the attribute
-
# has more than one error message, yields once for each error message.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.each do |attribute, error|
-
# # Will yield :name and "can't be blank"
-
# end
-
#
-
# person.errors.add(:name, "must be specified")
-
# person.errors.each do |attribute, error|
-
# # Will yield :name and "can't be blank"
-
# # then yield :name and "must be specified"
-
# end
-
1
def each
-
110
messages.each_key do |attribute|
-
self[attribute].each { |error| yield attribute, error }
-
end
-
end
-
-
# Returns the number of error messages.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.size # => 1
-
# person.errors.add(:name, "must be specified")
-
# person.errors.size # => 2
-
1
def size
-
values.flatten.size
-
end
-
-
# Returns all message values.
-
#
-
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
-
# person.errors.values # => [["cannot be nil", "must be specified"]]
-
1
def values
-
messages.values
-
end
-
-
# Returns all message keys.
-
#
-
# person.errors.messages # => {:name=>["cannot be nil", "must be specified"]}
-
# person.errors.keys # => [:name]
-
1
def keys
-
messages.keys
-
end
-
-
# Returns an array of error messages, with the attribute name included.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.add(:name, "must be specified")
-
# person.errors.to_a # => ["name can't be blank", "name must be specified"]
-
1
def to_a
-
full_messages
-
end
-
-
# Returns the number of error messages.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.count # => 1
-
# person.errors.add(:name, "must be specified")
-
# person.errors.count # => 2
-
1
def count
-
to_a.size
-
end
-
-
# Returns +true+ if no errors are found, +false+ otherwise.
-
# If the error message is a string it can be empty.
-
#
-
# person.errors.full_messages # => ["name cannot be nil"]
-
# person.errors.empty? # => false
-
1
def empty?
-
110
all? { |k, v| v && v.empty? && !v.is_a?(String) }
-
end
-
# aliases empty?
-
1
alias_method :blank?, :empty?
-
-
# Returns an xml formatted representation of the Errors hash.
-
#
-
# person.errors.add(:name, "can't be blank")
-
# person.errors.add(:name, "must be specified")
-
# person.errors.to_xml
-
# # =>
-
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
-
# # <errors>
-
# # <error>name can't be blank</error>
-
# # <error>name must be specified</error>
-
# # </errors>
-
1
def to_xml(options={})
-
to_a.to_xml({ root: "errors", skip_types: true }.merge!(options))
-
end
-
-
# Returns a Hash that can be used as the JSON representation for this
-
# object. You can pass the <tt>:full_messages</tt> option. This determines
-
# if the json object should contain full messages or not (false by default).
-
#
-
# person.errors.as_json # => {:name=>["cannot be nil"]}
-
# person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]}
-
1
def as_json(options=nil)
-
to_hash(options && options[:full_messages])
-
end
-
-
# Returns a Hash of attributes with their error messages. If +full_messages+
-
# is +true+, it will contain full messages (see +full_message+).
-
#
-
# person.errors.to_hash # => {:name=>["cannot be nil"]}
-
# person.errors.to_hash(true) # => {:name=>["name cannot be nil"]}
-
1
def to_hash(full_messages = false)
-
if full_messages
-
self.messages.each_with_object({}) do |(attribute, array), messages|
-
messages[attribute] = array.map { |message| full_message(attribute, message) }
-
end
-
else
-
self.messages.dup
-
end
-
end
-
-
# Adds +message+ to the error messages on +attribute+. More than one error
-
# can be added to the same +attribute+. If no +message+ is supplied,
-
# <tt>:invalid</tt> is assumed.
-
#
-
# person.errors.add(:name)
-
# # => ["is invalid"]
-
# person.errors.add(:name, 'must be implemented')
-
# # => ["is invalid", "must be implemented"]
-
#
-
# person.errors.messages
-
# # => {:name=>["must be implemented", "is invalid"]}
-
#
-
# If +message+ is a symbol, it will be translated using the appropriate
-
# scope (see +generate_message+).
-
#
-
# If +message+ is a proc, it will be called, allowing for things like
-
# <tt>Time.now</tt> to be used within an error.
-
#
-
# If the <tt>:strict</tt> option is set to +true+, it will raise
-
# ActiveModel::StrictValidationFailed instead of adding the error.
-
# <tt>:strict</tt> option can also be set to any other exception.
-
#
-
# person.errors.add(:name, nil, strict: true)
-
# # => ActiveModel::StrictValidationFailed: name is invalid
-
# person.errors.add(:name, nil, strict: NameIsInvalid)
-
# # => NameIsInvalid: name is invalid
-
#
-
# person.errors.messages # => {}
-
#
-
# +attribute+ should be set to <tt>:base</tt> if the error is not
-
# directly associated with a single attribute.
-
#
-
# person.errors.add(:base, "either name or email must be present")
-
# person.errors.messages
-
# # => {:base=>["either name or email must be present"]}
-
1
def add(attribute, message = :invalid, options = {})
-
message = normalize_message(attribute, message, options)
-
if exception = options[:strict]
-
exception = ActiveModel::StrictValidationFailed if exception == true
-
raise exception, full_message(attribute, message)
-
end
-
-
self[attribute] << message
-
end
-
-
# Will add an error message to each of the attributes in +attributes+
-
# that is empty.
-
#
-
# person.errors.add_on_empty(:name)
-
# person.errors.messages
-
# # => {:name=>["can't be empty"]}
-
1
def add_on_empty(attributes, options = {})
-
Array(attributes).each do |attribute|
-
value = @base.send(:read_attribute_for_validation, attribute)
-
is_empty = value.respond_to?(:empty?) ? value.empty? : false
-
add(attribute, :empty, options) if value.nil? || is_empty
-
end
-
end
-
-
# Will add an error message to each of the attributes in +attributes+ that
-
# is blank (using Object#blank?).
-
#
-
# person.errors.add_on_blank(:name)
-
# person.errors.messages
-
# # => {:name=>["can't be blank"]}
-
1
def add_on_blank(attributes, options = {})
-
Array(attributes).each do |attribute|
-
value = @base.send(:read_attribute_for_validation, attribute)
-
add(attribute, :blank, options) if value.blank?
-
end
-
end
-
-
# Returns +true+ if an error on the attribute with the given message is
-
# present, +false+ otherwise. +message+ is treated the same as for +add+.
-
#
-
# person.errors.add :name, :blank
-
# person.errors.added? :name, :blank # => true
-
1
def added?(attribute, message = :invalid, options = {})
-
message = normalize_message(attribute, message, options)
-
self[attribute].include? message
-
end
-
-
# Returns all the full error messages in an array.
-
#
-
# class Person
-
# validates_presence_of :name, :address, :email
-
# validates_length_of :name, in: 5..30
-
# end
-
#
-
# person = Person.create(address: '123 First St.')
-
# person.errors.full_messages
-
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
-
1
def full_messages
-
map { |attribute, message| full_message(attribute, message) }
-
end
-
-
# Returns all the full error messages for a given attribute in an array.
-
#
-
# class Person
-
# validates_presence_of :name, :email
-
# validates_length_of :name, in: 5..30
-
# end
-
#
-
# person = Person.create()
-
# person.errors.full_messages_for(:name)
-
# # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
-
1
def full_messages_for(attribute)
-
(get(attribute) || []).map { |message| full_message(attribute, message) }
-
end
-
-
# Returns a full message for a given attribute.
-
#
-
# person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
-
1
def full_message(attribute, message)
-
return message if attribute == :base
-
attr_name = attribute.to_s.tr('.', '_').humanize
-
attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
-
I18n.t(:"errors.format", {
-
default: "%{attribute} %{message}",
-
attribute: attr_name,
-
message: message
-
})
-
end
-
-
# Translates an error message in its default scope
-
# (<tt>activemodel.errors.messages</tt>).
-
#
-
# Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
-
# if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if
-
# that is not there also, it returns the translation of the default message
-
# (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model
-
# name, translated attribute name and the value are available for
-
# interpolation.
-
#
-
# When using inheritance in your models, it will check all the inherited
-
# models too, but only if the model itself hasn't been found. Say you have
-
# <tt>class Admin < User; end</tt> and you wanted the translation for
-
# the <tt>:blank</tt> error message for the <tt>title</tt> attribute,
-
# it looks for these translations:
-
#
-
# * <tt>activemodel.errors.models.admin.attributes.title.blank</tt>
-
# * <tt>activemodel.errors.models.admin.blank</tt>
-
# * <tt>activemodel.errors.models.user.attributes.title.blank</tt>
-
# * <tt>activemodel.errors.models.user.blank</tt>
-
# * any default you provided through the +options+ hash (in the <tt>activemodel.errors</tt> scope)
-
# * <tt>activemodel.errors.messages.blank</tt>
-
# * <tt>errors.attributes.title.blank</tt>
-
# * <tt>errors.messages.blank</tt>
-
1
def generate_message(attribute, type = :invalid, options = {})
-
type = options.delete(:message) if options[:message].is_a?(Symbol)
-
-
if @base.class.respond_to?(:i18n_scope)
-
defaults = @base.class.lookup_ancestors.map do |klass|
-
[ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
-
:"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
-
end
-
else
-
defaults = []
-
end
-
-
defaults << options.delete(:message)
-
defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
-
defaults << :"errors.attributes.#{attribute}.#{type}"
-
defaults << :"errors.messages.#{type}"
-
-
defaults.compact!
-
defaults.flatten!
-
-
key = defaults.shift
-
value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
-
-
options = {
-
default: defaults,
-
model: @base.model_name.human,
-
attribute: @base.class.human_attribute_name(attribute),
-
value: value
-
}.merge!(options)
-
-
I18n.translate(key, options)
-
end
-
-
1
private
-
1
def normalize_message(attribute, message, options)
-
case message
-
when Symbol
-
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
-
when Proc
-
message.call
-
else
-
message
-
end
-
end
-
end
-
-
# Raised when a validation cannot be corrected by end users and are considered
-
# exceptional.
-
#
-
# class Person
-
# include ActiveModel::Validations
-
#
-
# attr_accessor :name
-
#
-
# validates_presence_of :name, strict: true
-
# end
-
#
-
# person = Person.new
-
# person.name = nil
-
# person.valid?
-
# # => ActiveModel::StrictValidationFailed: Name can't be blank
-
1
class StrictValidationFailed < StandardError
-
end
-
end
-
1
require 'active_support/core_ext/string/conversions'
-
-
1
module ActiveRecord
-
1
module Associations
-
# Keeps track of table aliases for ActiveRecord::Associations::ClassMethods::JoinDependency and
-
# ActiveRecord::Associations::ThroughAssociationScope
-
1
class AliasTracker # :nodoc:
-
1
attr_reader :aliases, :connection
-
-
1
def self.empty(connection)
-
66
new connection, Hash.new(0)
-
end
-
-
1
def self.create(connection, table_joins)
-
42
if table_joins.empty?
-
42
empty connection
-
else
-
aliases = Hash.new { |h,k|
-
h[k] = initial_count_for(connection, k, table_joins)
-
}
-
new connection, aliases
-
end
-
end
-
-
1
def self.initial_count_for(connection, name, table_joins)
-
# quoted_name should be downcased as some database adapters (Oracle) return quoted name in uppercase
-
quoted_name = connection.quote_table_name(name).downcase
-
-
counts = table_joins.map do |join|
-
if join.is_a?(Arel::Nodes::StringJoin)
-
# Table names + table aliases
-
join.left.downcase.scan(
-
/join(?:\s+\w+)?\s+(\S+\s+)?#{quoted_name}\son/
-
).size
-
elsif join.respond_to? :left
-
join.left.table_name == name ? 1 : 0
-
else
-
# this branch is reached by two tests:
-
#
-
# activerecord/test/cases/associations/cascaded_eager_loading_test.rb:37
-
# with :posts
-
#
-
# activerecord/test/cases/associations/eager_test.rb:1133
-
# with :comments
-
#
-
0
-
end
-
end
-
-
counts.sum
-
end
-
-
# table_joins is an array of arel joins which might conflict with the aliases we assign here
-
1
def initialize(connection, aliases)
-
66
@aliases = aliases
-
66
@connection = connection
-
end
-
-
1
def aliased_table_for(table_name, aliased_name)
-
66
if aliases[table_name].zero?
-
# If it's zero, we can have our table_name
-
66
aliases[table_name] = 1
-
66
Arel::Table.new(table_name)
-
else
-
# Otherwise, we need to use an alias
-
aliased_name = connection.table_alias_for(aliased_name)
-
-
# Update the count
-
aliases[aliased_name] += 1
-
-
table_alias = if aliases[aliased_name] > 1
-
"#{truncate(aliased_name)}_#{aliases[aliased_name]}"
-
else
-
aliased_name
-
end
-
Arel::Table.new(table_name).alias(table_alias)
-
end
-
end
-
-
1
private
-
-
1
def truncate(name)
-
name.slice(0, connection.table_alias_length - 2)
-
end
-
end
-
end
-
end
-
1
require 'active_support/core_ext/array/wrap'
-
-
1
module ActiveRecord
-
1
module Associations
-
# = Active Record Associations
-
#
-
# This is the root class of all associations ('+ Foo' signifies an included module Foo):
-
#
-
# Association
-
# SingularAssociation
-
# HasOneAssociation
-
# HasOneThroughAssociation + ThroughAssociation
-
# BelongsToAssociation
-
# BelongsToPolymorphicAssociation
-
# CollectionAssociation
-
# HasManyAssociation
-
# HasManyThroughAssociation + ThroughAssociation
-
1
class Association #:nodoc:
-
1
attr_reader :owner, :target, :reflection
-
1
attr_accessor :inversed
-
-
1
delegate :options, :to => :reflection
-
-
1
def initialize(owner, reflection)
-
61
reflection.check_validity!
-
-
61
@owner, @reflection = owner, reflection
-
-
61
reset
-
61
reset_scope
-
end
-
-
# Returns the name of the table of the associated class:
-
#
-
# post.comments.aliased_table_name # => "comments"
-
#
-
1
def aliased_table_name
-
klass.table_name
-
end
-
-
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
-
1
def reset
-
73
@loaded = false
-
73
@target = nil
-
73
@stale_state = nil
-
73
@inversed = false
-
end
-
-
# Reloads the \target and returns +self+ on success.
-
1
def reload
-
12
reset
-
12
reset_scope
-
12
load_target
-
12
self unless target.nil?
-
end
-
-
# Has the \target been already \loaded?
-
1
def loaded?
-
285
@loaded
-
end
-
-
# Asserts the \target has been loaded setting the \loaded flag to +true+.
-
1
def loaded!
-
92
@loaded = true
-
92
@stale_state = stale_state
-
92
@inversed = false
-
end
-
-
# The target is stale if the target no longer points to the record(s) that the
-
# relevant foreign_key(s) refers to. If stale, the association accessor method
-
# on the owner will reload the target. It's up to subclasses to implement the
-
# stale_state method if relevant.
-
#
-
# Note that if the target has not been loaded, it is not considered stale.
-
1
def stale_target?
-
103
!inversed && loaded? && @stale_state != stale_state
-
end
-
-
# Sets the target of this association to <tt>\target</tt>, and the \loaded flag to +true+.
-
1
def target=(target)
-
40
@target = target
-
40
loaded!
-
end
-
-
1
def scope
-
30
target_scope.merge(association_scope)
-
end
-
-
# The scope for this association.
-
#
-
# Note that the association_scope is merged into the target_scope only when the
-
# scope method is called. This is because at that point the call may be surrounded
-
# by scope.scoping { ... } or with_scope { ... } etc, which affects the scope which
-
# actually gets built.
-
1
def association_scope
-
39
if klass
-
39
@association_scope ||= AssociationScope.scope(self, klass.connection)
-
end
-
end
-
-
1
def reset_scope
-
77
@association_scope = nil
-
end
-
-
# Set the inverse association, if possible
-
1
def set_inverse_instance(record)
-
79
if invertible_for?(record)
-
inverse = record.association(inverse_reflection_for(record).name)
-
inverse.target = owner
-
inverse.inversed = true
-
end
-
79
record
-
end
-
-
# Returns the class of the target. belongs_to polymorphic overrides this to look at the
-
# polymorphic_type field on the owner.
-
1
def klass
-
264
reflection.klass
-
end
-
-
# Can be overridden (i.e. in ThroughAssociation) to merge in other scopes (i.e. the
-
# through association's scope)
-
1
def target_scope
-
33
AssociationRelation.create(klass, klass.arel_table, self).merge!(klass.all)
-
end
-
-
# Loads the \target if needed and returns it.
-
#
-
# This method is abstract in the sense that it relies on +find_target+,
-
# which is expected to be provided by descendants.
-
#
-
# If the \target is already \loaded it is just returned. Thus, you can call
-
# +load_target+ unconditionally to get the \target.
-
#
-
# ActiveRecord::RecordNotFound is rescued within the method, and it is
-
# not reraised. The proxy is \reset and +nil+ is the return value.
-
1
def load_target
-
64
@target = find_target if (@stale_state && stale_target?) || find_target?
-
-
64
loaded! unless loaded?
-
64
target
-
rescue ActiveRecord::RecordNotFound
-
reset
-
end
-
-
1
def interpolate(sql, record = nil)
-
if sql.respond_to?(:to_proc)
-
owner.instance_exec(record, &sql)
-
else
-
sql
-
end
-
end
-
-
# We can't dump @reflection since it contains the scope proc
-
1
def marshal_dump
-
ivars = (instance_variables - [:@reflection]).map { |name| [name, instance_variable_get(name)] }
-
[@reflection.name, ivars]
-
end
-
-
1
def marshal_load(data)
-
reflection_name, ivars = data
-
ivars.each { |name, val| instance_variable_set(name, val) }
-
@reflection = @owner.class._reflect_on_association(reflection_name)
-
end
-
-
1
def initialize_attributes(record) #:nodoc:
-
9
skip_assign = [reflection.foreign_key, reflection.type].compact
-
9
attributes = create_scope.except(*(record.changed - skip_assign))
-
9
record.assign_attributes(attributes)
-
9
set_inverse_instance(record)
-
end
-
-
1
private
-
-
1
def find_target?
-
!loaded? && (!owner.new_record? || foreign_key_present?) && klass
-
end
-
-
1
def creation_attributes
-
9
attributes = {}
-
-
9
if (reflection.has_one? || reflection.collection?) && !options[:through]
-
9
attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
-
-
9
if reflection.options[:as]
-
9
attributes[reflection.type] = owner.class.base_class.name
-
end
-
end
-
-
9
attributes
-
end
-
-
# Sets the owner attributes on the given record
-
1
def set_owner_attributes(record)
-
27
creation_attributes.each { |key, value| record[key] = value }
-
end
-
-
# Returns true if there is a foreign key present on the owner which
-
# references the target. This is used to determine whether we can load
-
# the target if the owner is currently a new record (and therefore
-
# without a key). If the owner is a new record then foreign_key must
-
# be present in order to load target.
-
#
-
# Currently implemented by belongs_to (vanilla and polymorphic) and
-
# has_one/has_many :through associations which go through a belongs_to.
-
1
def foreign_key_present?
-
false
-
end
-
-
# Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
-
# the kind of the class of the associated objects. Meant to be used as
-
# a sanity check when you are about to assign an associated record.
-
1
def raise_on_type_mismatch!(record)
-
22
unless record.is_a?(reflection.klass) || record.is_a?(reflection.class_name.constantize)
-
message = "#{reflection.class_name}(##{reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
-
raise ActiveRecord::AssociationTypeMismatch, message
-
end
-
end
-
-
# Can be redefined by subclasses, notably polymorphic belongs_to
-
# The record parameter is necessary to support polymorphic inverses as we must check for
-
# the association in the specific class of the record.
-
1
def inverse_reflection_for(record)
-
61
reflection.inverse_of
-
end
-
-
# Returns true if inverse association on the given record needs to be set.
-
# This method is redefined by subclasses.
-
1
def invertible_for?(record)
-
27
foreign_key_for?(record) && inverse_reflection_for(record)
-
end
-
-
# Returns true if record contains the foreign_key
-
1
def foreign_key_for?(record)
-
27
record.has_attribute?(reflection.foreign_key)
-
end
-
-
# This should be implemented to return the values of the relevant key(s) on the owner,
-
# so that when stale_state is different from the value stored on the last find_target,
-
# the target is stale.
-
#
-
# This is only relevant to certain associations, which is why it returns nil by default.
-
1
def stale_state
-
end
-
-
1
def build_record(attributes)
-
9
reflection.build_association(attributes) do |record|
-
9
initialize_attributes(record)
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
1
class AssociationScope #:nodoc:
-
1
def self.scope(association, connection)
-
21
INSTANCE.scope association, connection
-
end
-
-
1
class BindSubstitution
-
1
def initialize(block)
-
4
@block = block
-
end
-
-
1
def bind_value(scope, column, value, alias_tracker)
-
33
substitute = alias_tracker.connection.substitute_at(column)
-
33
scope.bind_values += [[column, @block.call(value)]]
-
33
substitute
-
end
-
end
-
-
1
def self.create(&block)
-
34
block = block ? block : lambda { |val| val }
-
4
new BindSubstitution.new(block)
-
end
-
-
1
def initialize(bind_substitution)
-
4
@bind_substitution = bind_substitution
-
end
-
-
1
INSTANCE = create
-
-
1
def scope(association, connection)
-
24
klass = association.klass
-
24
reflection = association.reflection
-
24
scope = klass.unscoped
-
24
owner = association.owner
-
24
alias_tracker = AliasTracker.empty connection
-
-
24
scope.extending! Array(reflection.options[:extend])
-
24
add_constraints(scope, owner, klass, reflection, alias_tracker)
-
end
-
-
1
def join_type
-
Arel::Nodes::InnerJoin
-
end
-
-
1
def self.get_bind_values(owner, chain)
-
12
binds = []
-
12
last_reflection = chain.last
-
-
12
binds << last_reflection.join_id_for(owner)
-
12
if last_reflection.type
-
binds << owner.class.base_class.name
-
end
-
-
12
chain.each_cons(2).each do |reflection, next_reflection|
-
if reflection.type
-
binds << next_reflection.klass.base_class.name
-
end
-
end
-
12
binds
-
end
-
-
1
private
-
-
1
def construct_tables(chain, klass, refl, alias_tracker)
-
24
chain.map do |reflection|
-
24
alias_tracker.aliased_table_for(
-
table_name_for(reflection, klass, refl),
-
table_alias_for(reflection, refl, reflection != refl)
-
)
-
end
-
end
-
-
1
def table_alias_for(reflection, refl, join = false)
-
24
name = "#{reflection.plural_name}_#{alias_suffix(refl)}"
-
24
name << "_join" if join
-
24
name
-
end
-
-
1
def join(table, constraint)
-
table.create_join(table, table.create_on(constraint), join_type)
-
end
-
-
1
def column_for(table_name, column_name, alias_tracker)
-
33
columns = alias_tracker.connection.schema_cache.columns_hash(table_name)
-
33
columns[column_name]
-
end
-
-
1
def bind_value(scope, column, value, alias_tracker)
-
33
@bind_substitution.bind_value scope, column, value, alias_tracker
-
end
-
-
1
def bind(scope, table_name, column_name, value, tracker)
-
33
column = column_for table_name, column_name, tracker
-
33
bind_value scope, column, value, tracker
-
end
-
-
1
def last_chain_scope(scope, table, reflection, owner, tracker, assoc_klass)
-
24
join_keys = reflection.join_keys(assoc_klass)
-
24
key = join_keys.key
-
24
foreign_key = join_keys.foreign_key
-
-
24
bind_val = bind scope, table.table_name, key.to_s, owner[foreign_key], tracker
-
24
scope = scope.where(table[key].eq(bind_val))
-
-
24
if reflection.type
-
9
value = owner.class.base_class.name
-
9
bind_val = bind scope, table.table_name, reflection.type, value, tracker
-
9
scope = scope.where(table[reflection.type].eq(bind_val))
-
else
-
15
scope
-
end
-
end
-
-
1
def next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
-
join_keys = reflection.join_keys(assoc_klass)
-
key = join_keys.key
-
foreign_key = join_keys.foreign_key
-
-
constraint = table[key].eq(foreign_table[foreign_key])
-
-
if reflection.type
-
value = next_reflection.klass.base_class.name
-
bind_val = bind scope, table.table_name, reflection.type, value, tracker
-
scope = scope.where(table[reflection.type].eq(bind_val))
-
end
-
-
scope = scope.joins(join(foreign_table, constraint))
-
end
-
-
1
def add_constraints(scope, owner, assoc_klass, refl, tracker)
-
24
chain = refl.chain
-
24
scope_chain = refl.scope_chain
-
-
24
tables = construct_tables(chain, assoc_klass, refl, tracker)
-
-
24
owner_reflection = chain.last
-
24
table = tables.last
-
24
scope = last_chain_scope(scope, table, owner_reflection, owner, tracker, assoc_klass)
-
-
24
chain.each_with_index do |reflection, i|
-
24
table, foreign_table = tables.shift, tables.first
-
-
24
unless reflection == chain.last
-
next_reflection = chain[i + 1]
-
scope = next_chain_scope(scope, table, reflection, tracker, assoc_klass, foreign_table, next_reflection)
-
end
-
-
24
is_first_chain = i == 0
-
24
klass = is_first_chain ? assoc_klass : reflection.klass
-
-
# Exclude the scope of the association itself, because that
-
# was already merged in the #scope method.
-
24
scope_chain[i].each do |scope_chain_item|
-
item = eval_scope(klass, scope_chain_item, owner)
-
-
if scope_chain_item == refl.scope
-
scope.merge! item.except(:where, :includes, :bind)
-
end
-
-
if is_first_chain
-
scope.includes! item.includes_values
-
end
-
-
scope.where_values += item.where_values
-
scope.bind_values += item.bind_values
-
scope.order_values |= item.order_values
-
end
-
end
-
-
24
scope
-
end
-
-
1
def alias_suffix(refl)
-
24
refl.name
-
end
-
-
1
def table_name_for(reflection, klass, refl)
-
24
if reflection == refl
-
# If this is a polymorphic belongs_to, we want to get the klass from the
-
# association because it depends on the polymorphic_type attribute of
-
# the owner
-
24
klass.table_name
-
else
-
reflection.table_name
-
end
-
end
-
-
1
def eval_scope(klass, scope, owner)
-
klass.unscoped.instance_exec(owner, &scope)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Belongs To Polymorphic Association
-
1
module Associations
-
1
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
-
1
def klass
-
type = owner[reflection.foreign_type]
-
type.presence && type.constantize
-
end
-
-
1
private
-
-
1
def replace_keys(record)
-
18
super
-
18
owner[reflection.foreign_type] = record.class.base_class.name
-
end
-
-
1
def remove_keys
-
super
-
owner[reflection.foreign_type] = nil
-
end
-
-
1
def different_target?(record)
-
super || record.class != klass
-
end
-
-
1
def inverse_reflection_for(record)
-
18
reflection.polymorphic_inverse_of(record.class)
-
end
-
-
1
def raise_on_type_mismatch!(record)
-
# A polymorphic association cannot have a type mismatch, by definition
-
end
-
-
1
def stale_state
-
54
foreign_key = super
-
54
foreign_key && [foreign_key.to_s, owner[reflection.foreign_type].to_s]
-
end
-
end
-
end
-
end
-
1
module ActiveRecord::Associations::Builder
-
1
class BelongsTo < SingularAssociation #:nodoc:
-
1
def macro
-
7
:belongs_to
-
end
-
-
1
def valid_options
-
7
super + [:foreign_type, :polymorphic, :touch, :counter_cache]
-
end
-
-
1
def self.valid_dependent_options
-
[:destroy, :delete]
-
end
-
-
1
def self.define_callbacks(model, reflection)
-
7
super
-
7
add_counter_cache_callbacks(model, reflection) if reflection.options[:counter_cache]
-
7
add_touch_callbacks(model, reflection) if reflection.options[:touch]
-
end
-
-
1
def self.define_accessors(mixin, reflection)
-
7
super
-
7
add_counter_cache_methods mixin
-
end
-
-
1
private
-
-
1
def self.add_counter_cache_methods(mixin)
-
7
return if mixin.method_defined? :belongs_to_counter_cache_after_update
-
-
3
mixin.class_eval do
-
3
def belongs_to_counter_cache_after_update(reflection)
-
foreign_key = reflection.foreign_key
-
cache_column = reflection.counter_cache_column
-
-
if (@_after_create_counter_called ||= false)
-
@_after_create_counter_called = false
-
elsif attribute_changed?(foreign_key) && !new_record? && reflection.constructable?
-
model = reflection.klass
-
foreign_key_was = attribute_was foreign_key
-
foreign_key = attribute foreign_key
-
-
if foreign_key && model.respond_to?(:increment_counter)
-
model.increment_counter(cache_column, foreign_key)
-
end
-
if foreign_key_was && model.respond_to?(:decrement_counter)
-
model.decrement_counter(cache_column, foreign_key_was)
-
end
-
end
-
end
-
end
-
end
-
-
1
def self.add_counter_cache_callbacks(model, reflection)
-
cache_column = reflection.counter_cache_column
-
-
model.after_update lambda { |record|
-
record.belongs_to_counter_cache_after_update(reflection)
-
}
-
-
klass = reflection.class_name.safe_constantize
-
klass.attr_readonly cache_column if klass && klass.respond_to?(:attr_readonly)
-
end
-
-
1
def self.touch_record(o, foreign_key, name, touch) # :nodoc:
-
old_foreign_id = o.changed_attributes[foreign_key]
-
-
if old_foreign_id
-
association = o.association(name)
-
reflection = association.reflection
-
if reflection.polymorphic?
-
klass = o.public_send("#{reflection.foreign_type}_was").constantize
-
else
-
klass = association.klass
-
end
-
old_record = klass.find_by(klass.primary_key => old_foreign_id)
-
-
if old_record
-
if touch != true
-
old_record.touch touch
-
else
-
old_record.touch
-
end
-
end
-
end
-
-
record = o.send name
-
if record && record.persisted?
-
if touch != true
-
record.touch touch
-
else
-
record.touch
-
end
-
end
-
end
-
-
1
def self.add_touch_callbacks(model, reflection)
-
foreign_key = reflection.foreign_key
-
n = reflection.name
-
touch = reflection.options[:touch]
-
-
callback = lambda { |record|
-
BelongsTo.touch_record(record, foreign_key, n, touch)
-
}
-
-
model.after_save callback, if: :changed?
-
model.after_touch callback
-
model.after_destroy callback
-
end
-
-
1
def self.add_destroy_callbacks(model, reflection)
-
name = reflection.name
-
model.after_destroy lambda { |o| o.association(name).handle_dependency }
-
end
-
end
-
end
-
# This class is inherited by the has_one and belongs_to association classes
-
-
1
module ActiveRecord::Associations::Builder
-
1
class SingularAssociation < Association #:nodoc:
-
1
def valid_options
-
7
super + [:dependent, :primary_key, :inverse_of, :required]
-
end
-
-
1
def self.define_accessors(model, reflection)
-
7
super
-
7
define_constructors(model.generated_association_methods, reflection.name) if reflection.constructable?
-
end
-
-
# Defines the (build|create)_association methods for belongs_to or has_one association
-
1
def self.define_constructors(mixin, name)
-
4
mixin.class_eval <<-CODE, __FILE__, __LINE__ + 1
-
def build_#{name}(*args, &block)
-
association(:#{name}).build(*args, &block)
-
end
-
-
def create_#{name}(*args, &block)
-
association(:#{name}).create(*args, &block)
-
end
-
-
def create_#{name}!(*args, &block)
-
association(:#{name}).create!(*args, &block)
-
end
-
CODE
-
end
-
-
1
def self.define_validations(model, reflection)
-
7
super
-
7
if reflection.options[:required]
-
3
model.validates_presence_of reflection.name
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
# = Active Record Association Collection
-
#
-
# CollectionAssociation is an abstract class that provides common stuff to
-
# ease the implementation of association proxies that represent
-
# collections. See the class hierarchy in Association.
-
#
-
# CollectionAssociation:
-
# HasManyAssociation => has_many
-
# HasManyThroughAssociation + ThroughAssociation => has_many :through
-
#
-
# CollectionAssociation class provides common methods to the collections
-
# defined by +has_and_belongs_to_many+, +has_many+ or +has_many+ with
-
# +:through association+ option.
-
#
-
# You need to be careful with assumptions regarding the target: The proxy
-
# does not fetch records from the database until it needs them, but new
-
# ones created with +build+ are added to the target. So, the target may be
-
# non-empty and still lack children waiting to be read from the database.
-
# If you look directly to the database you cannot assume that's the entire
-
# collection because new records may have been added to the target, etc.
-
#
-
# If you need to work on all current children, new and existing records,
-
# +load_target+ and the +loaded+ flag are your friends.
-
1
class CollectionAssociation < Association #:nodoc:
-
-
# Implements the reader method, e.g. foo.items for Foo.has_many :items
-
1
def reader(force_reload = false)
-
9
if force_reload
-
klass.uncached { reload }
-
elsif stale_target?
-
reload
-
end
-
-
9
if owner.new_record?
-
# Cache the proxy separately before the owner has an id
-
# or else a post-save proxy will still lack the id
-
@new_record_proxy ||= CollectionProxy.create(klass, self)
-
else
-
9
@proxy ||= CollectionProxy.create(klass, self)
-
end
-
end
-
-
# Implements the writer method, e.g. foo.items= for Foo.has_many :items
-
1
def writer(records)
-
replace(records)
-
end
-
-
# Implements the ids reader method, e.g. foo.item_ids for Foo.has_many :items
-
1
def ids_reader
-
if loaded?
-
load_target.map do |record|
-
record.send(reflection.association_primary_key)
-
end
-
else
-
column = "#{reflection.quoted_table_name}.#{reflection.association_primary_key}"
-
scope.pluck(column)
-
end
-
end
-
-
# Implements the ids writer method, e.g. foo.item_ids= for Foo.has_many :items
-
1
def ids_writer(ids)
-
pk_type = reflection.primary_key_type
-
ids = Array(ids).reject { |id| id.blank? }
-
ids.map! { |i| pk_type.type_cast_from_user(i) }
-
replace(klass.find(ids).index_by { |r| r.id }.values_at(*ids))
-
end
-
-
1
def reset
-
9
super
-
9
@target = []
-
end
-
-
1
def select(*fields)
-
if block_given?
-
load_target.select.each { |e| yield e }
-
else
-
scope.select(*fields)
-
end
-
end
-
-
1
def find(*args)
-
if block_given?
-
load_target.find(*args) { |*block_args| yield(*block_args) }
-
else
-
if options[:inverse_of] && loaded?
-
args_flatten = args.flatten
-
raise RecordNotFound, "Couldn't find #{scope.klass.name} without an ID" if args_flatten.blank?
-
result = find_by_scan(*args)
-
-
result_size = Array(result).size
-
if !result || result_size != args_flatten.size
-
scope.raise_record_not_found_exception!(args_flatten, result_size, args_flatten.size)
-
else
-
result
-
end
-
else
-
scope.find(*args)
-
end
-
end
-
end
-
-
1
def first(*args)
-
first_nth_or_last(:first, *args)
-
end
-
-
1
def second(*args)
-
first_nth_or_last(:second, *args)
-
end
-
-
1
def third(*args)
-
first_nth_or_last(:third, *args)
-
end
-
-
1
def fourth(*args)
-
first_nth_or_last(:fourth, *args)
-
end
-
-
1
def fifth(*args)
-
first_nth_or_last(:fifth, *args)
-
end
-
-
1
def forty_two(*args)
-
first_nth_or_last(:forty_two, *args)
-
end
-
-
1
def last(*args)
-
first_nth_or_last(:last, *args)
-
end
-
-
1
def take(n = nil)
-
if loaded?
-
n ? target.take(n) : target.first
-
else
-
scope.take(n).tap do |record|
-
set_inverse_instance record if record.is_a? ActiveRecord::Base
-
end
-
end
-
end
-
-
1
def build(attributes = {}, &block)
-
if attributes.is_a?(Array)
-
attributes.collect { |attr| build(attr, &block) }
-
else
-
add_to_target(build_record(attributes)) do |record|
-
yield(record) if block_given?
-
end
-
end
-
end
-
-
1
def create(attributes = {}, &block)
-
9
_create_record(attributes, &block)
-
end
-
-
1
def create!(attributes = {}, &block)
-
_create_record(attributes, true, &block)
-
end
-
-
# Add +records+ to this association. Returns +self+ so method calls may
-
# be chained. Since << flattens its argument list and inserts each record,
-
# +push+ and +concat+ behave identically.
-
1
def concat(*records)
-
if owner.new_record?
-
load_target
-
concat_records(records)
-
else
-
transaction { concat_records(records) }
-
end
-
end
-
-
# Starts a transaction in the association class's database connection.
-
#
-
# class Author < ActiveRecord::Base
-
# has_many :books
-
# end
-
#
-
# Author.first.books.transaction do
-
# # same effect as calling Book.transaction
-
# end
-
1
def transaction(*args)
-
9
reflection.klass.transaction(*args) do
-
9
yield
-
end
-
end
-
-
# Removes all records from the association without calling callbacks
-
# on the associated records. It honors the +:dependent+ option. However
-
# if the +:dependent+ value is +:destroy+ then in that case the +:delete_all+
-
# deletion strategy for the association is applied.
-
#
-
# You can force a particular deletion strategy by passing a parameter.
-
#
-
# Example:
-
#
-
# @author.books.delete_all(:nullify)
-
# @author.books.delete_all(:delete_all)
-
#
-
# See delete for more info.
-
1
def delete_all(dependent = nil)
-
if dependent && ![:nullify, :delete_all].include?(dependent)
-
raise ArgumentError, "Valid values are :nullify or :delete_all"
-
end
-
-
dependent = if dependent
-
dependent
-
elsif options[:dependent] == :destroy
-
:delete_all
-
else
-
options[:dependent]
-
end
-
-
delete_or_nullify_all_records(dependent).tap do
-
reset
-
loaded!
-
end
-
end
-
-
# Destroy all the records from this association.
-
#
-
# See destroy for more info.
-
1
def destroy_all
-
destroy(load_target).tap do
-
reset
-
loaded!
-
end
-
end
-
-
# Count all records using SQL. Construct options and pass them with
-
# scope to the target class's +count+.
-
1
def count(column_name = nil, count_options = {})
-
# TODO: Remove count_options argument as soon we remove support to
-
# activerecord-deprecated_finders.
-
column_name, count_options = nil, column_name if column_name.is_a?(Hash)
-
-
relation = scope
-
if association_scope.distinct_value
-
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL.
-
column_name ||= reflection.klass.primary_key
-
relation = relation.distinct
-
end
-
-
value = relation.count(column_name)
-
-
limit = options[:limit]
-
offset = options[:offset]
-
-
if limit || offset
-
[ [value - offset.to_i, 0].max, limit.to_i ].min
-
else
-
value
-
end
-
end
-
-
# Removes +records+ from this association calling +before_remove+ and
-
# +after_remove+ callbacks.
-
#
-
# This method is abstract in the sense that +delete_records+ has to be
-
# provided by descendants. Note this method does not imply the records
-
# are actually removed from the database, that depends precisely on
-
# +delete_records+. They are in any case removed from the collection.
-
1
def delete(*records)
-
return if records.empty?
-
_options = records.extract_options!
-
dependent = _options[:dependent] || options[:dependent]
-
-
records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
-
delete_or_destroy(records, dependent)
-
end
-
-
# Deletes the +records+ and removes them from this association calling
-
# +before_remove+ , +after_remove+ , +before_destroy+ and +after_destroy+ callbacks.
-
#
-
# Note that this method removes records from the database ignoring the
-
# +:dependent+ option.
-
1
def destroy(*records)
-
return if records.empty?
-
records = find(records) if records.any? { |record| record.kind_of?(Fixnum) || record.kind_of?(String) }
-
delete_or_destroy(records, :destroy)
-
end
-
-
# Returns the size of the collection by executing a SELECT COUNT(*)
-
# query if the collection hasn't been loaded, and calling
-
# <tt>collection.size</tt> if it has.
-
#
-
# If the collection has been already loaded +size+ and +length+ are
-
# equivalent. If not and you are going to need the records anyway
-
# +length+ will take one less query. Otherwise +size+ is more efficient.
-
#
-
# This method is abstract in the sense that it relies on
-
# +count_records+, which is a method descendants have to provide.
-
1
def size
-
if !find_target? || loaded?
-
if association_scope.distinct_value
-
target.uniq.size
-
else
-
target.size
-
end
-
elsif !loaded? && !association_scope.group_values.empty?
-
load_target.size
-
elsif !loaded? && !association_scope.distinct_value && target.is_a?(Array)
-
unsaved_records = target.select { |r| r.new_record? }
-
unsaved_records.size + count_records
-
else
-
count_records
-
end
-
end
-
-
# Returns the size of the collection calling +size+ on the target.
-
#
-
# If the collection has been already loaded +length+ and +size+ are
-
# equivalent. If not and you are going to need the records anyway this
-
# method will take one less query. Otherwise +size+ is more efficient.
-
1
def length
-
load_target.size
-
end
-
-
# Returns true if the collection is empty.
-
#
-
# If the collection has been loaded
-
# it is equivalent to <tt>collection.size.zero?</tt>. If the
-
# collection has not been loaded, it is equivalent to
-
# <tt>collection.exists?</tt>. If the collection has not already been
-
# loaded and you are going to fetch the records anyway it is better to
-
# check <tt>collection.length.zero?</tt>.
-
1
def empty?
-
if loaded?
-
size.zero?
-
else
-
@target.blank? && !scope.exists?
-
end
-
end
-
-
# Returns true if the collections is not empty.
-
# Equivalent to +!collection.empty?+.
-
1
def any?
-
if block_given?
-
load_target.any? { |*block_args| yield(*block_args) }
-
else
-
!empty?
-
end
-
end
-
-
# Returns true if the collection has more than 1 record.
-
# Equivalent to +collection.size > 1+.
-
1
def many?
-
if block_given?
-
load_target.many? { |*block_args| yield(*block_args) }
-
else
-
size > 1
-
end
-
end
-
-
1
def distinct
-
seen = {}
-
load_target.find_all do |record|
-
seen[record.id] = true unless seen.key?(record.id)
-
end
-
end
-
1
alias uniq distinct
-
-
# Replace this collection with +other_array+. This will perform a diff
-
# and delete/add only records that have changed.
-
1
def replace(other_array)
-
other_array.each { |val| raise_on_type_mismatch!(val) }
-
original_target = load_target.dup
-
-
if owner.new_record?
-
replace_records(other_array, original_target)
-
else
-
replace_common_records_in_memory(other_array, original_target)
-
if other_array != original_target
-
transaction { replace_records(other_array, original_target) }
-
end
-
end
-
end
-
-
1
def include?(record)
-
if record.is_a?(reflection.klass)
-
if record.new_record?
-
include_in_memory?(record)
-
else
-
loaded? ? target.include?(record) : scope.exists?(record.id)
-
end
-
else
-
false
-
end
-
end
-
-
1
def load_target
-
if find_target?
-
@target = merge_target_lists(find_target, target)
-
end
-
-
loaded!
-
target
-
end
-
-
1
def add_to_target(record, skip_callbacks = false, &block)
-
9
if association_scope.distinct_value
-
index = @target.index(record)
-
end
-
9
replace_on_target(record, index, skip_callbacks, &block)
-
end
-
-
1
def replace_on_target(record, index, skip_callbacks)
-
9
callback(:before_add, record) unless skip_callbacks
-
9
yield(record) if block_given?
-
-
9
if index
-
@target[index] = record
-
else
-
9
@target << record
-
end
-
-
9
callback(:after_add, record) unless skip_callbacks
-
9
set_inverse_instance(record)
-
-
9
record
-
end
-
-
1
def scope(opts = {})
-
18
scope = super()
-
18
scope.none! if opts.fetch(:nullify, true) && null_scope?
-
18
scope
-
end
-
-
1
def null_scope?
-
9
owner.new_record? && !foreign_key_present?
-
end
-
-
1
private
-
1
def get_records
-
if reflection.scope_chain.any?(&:any?) ||
-
scope.eager_loading? ||
-
klass.current_scope ||
-
klass.default_scopes.any?
-
-
return scope.to_a
-
end
-
-
conn = klass.connection
-
sc = reflection.association_scope_cache(conn, owner) do
-
StatementCache.create(conn) { |params|
-
as = AssociationScope.create { params.bind }
-
target_scope.merge as.scope(self, conn)
-
}
-
end
-
-
binds = AssociationScope.get_bind_values(owner, reflection.chain)
-
sc.execute binds, klass, klass.connection
-
end
-
-
1
def find_target
-
records = get_records
-
records.each { |record| set_inverse_instance(record) }
-
records
-
end
-
-
# We have some records loaded from the database (persisted) and some that are
-
# in-memory (memory). The same record may be represented in the persisted array
-
# and in the memory array.
-
#
-
# So the task of this method is to merge them according to the following rules:
-
#
-
# * The final array must not have duplicates
-
# * The order of the persisted array is to be preserved
-
# * Any changes made to attributes on objects in the memory array are to be preserved
-
# * Otherwise, attributes should have the value found in the database
-
1
def merge_target_lists(persisted, memory)
-
return persisted if memory.empty?
-
return memory if persisted.empty?
-
-
persisted.map! do |record|
-
if mem_record = memory.delete(record)
-
-
((record.attribute_names & mem_record.attribute_names) - mem_record.changes.keys).each do |name|
-
mem_record[name] = record[name]
-
end
-
-
mem_record
-
else
-
record
-
end
-
end
-
-
persisted + memory
-
end
-
-
1
def _create_record(attributes, raise = false, &block)
-
9
unless owner.persisted?
-
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
-
end
-
-
9
if attributes.is_a?(Array)
-
attributes.collect { |attr| _create_record(attr, raise, &block) }
-
else
-
9
transaction do
-
9
add_to_target(build_record(attributes)) do |record|
-
9
yield(record) if block_given?
-
9
insert_record(record, true, raise)
-
end
-
end
-
end
-
end
-
-
# Do the relevant stuff to insert the given record into the association collection.
-
1
def insert_record(record, validate = true, raise = false)
-
raise NotImplementedError
-
end
-
-
1
def create_scope
-
9
scope.scope_for_create.stringify_keys
-
end
-
-
1
def delete_or_destroy(records, method)
-
records = records.flatten
-
records.each { |record| raise_on_type_mismatch!(record) }
-
existing_records = records.reject { |r| r.new_record? }
-
-
if existing_records.empty?
-
remove_records(existing_records, records, method)
-
else
-
transaction { remove_records(existing_records, records, method) }
-
end
-
end
-
-
1
def remove_records(existing_records, records, method)
-
records.each { |record| callback(:before_remove, record) }
-
-
delete_records(existing_records, method) if existing_records.any?
-
records.each { |record| target.delete(record) }
-
-
records.each { |record| callback(:after_remove, record) }
-
end
-
-
# Delete the given records from the association, using one of the methods :destroy,
-
# :delete_all or :nullify (or nil, in which case a default is used).
-
1
def delete_records(records, method)
-
raise NotImplementedError
-
end
-
-
1
def replace_records(new_target, original_target)
-
delete(target - new_target)
-
-
unless concat(new_target - target)
-
@target = original_target
-
raise RecordNotSaved, "Failed to replace #{reflection.name} because one or more of the " \
-
"new records could not be saved."
-
end
-
-
target
-
end
-
-
1
def replace_common_records_in_memory(new_target, original_target)
-
common_records = new_target & original_target
-
common_records.each do |record|
-
skip_callbacks = true
-
replace_on_target(record, @target.index(record), skip_callbacks)
-
end
-
end
-
-
1
def concat_records(records, should_raise = false)
-
result = true
-
-
records.flatten.each do |record|
-
raise_on_type_mismatch!(record)
-
add_to_target(record) do |rec|
-
result &&= insert_record(rec, true, should_raise) unless owner.new_record?
-
end
-
end
-
-
result && records
-
end
-
-
1
def callback(method, record)
-
18
callbacks_for(method).each do |callback|
-
callback.call(method, owner, record)
-
end
-
end
-
-
1
def callbacks_for(callback_name)
-
18
full_callback_name = "#{callback_name}_for_#{reflection.name}"
-
18
owner.class.send(full_callback_name)
-
end
-
-
# Should we deal with assoc.first or assoc.last by issuing an independent query to
-
# the database, or by getting the target, and then taking the first/last item from that?
-
#
-
# If the args is just a non-empty options hash, go to the database.
-
#
-
# Otherwise, go to the database only if none of the following are true:
-
# * target already loaded
-
# * owner is new record
-
# * target contains new or changed record(s)
-
1
def fetch_first_nth_or_last_using_find?(args)
-
if args.first.is_a?(Hash)
-
true
-
else
-
!(loaded? ||
-
owner.new_record? ||
-
target.any? { |record| record.new_record? || record.changed? })
-
end
-
end
-
-
1
def include_in_memory?(record)
-
if reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
-
assoc = owner.association(reflection.through_reflection.name)
-
assoc.reader.any? { |source|
-
target_association = source.send(reflection.source_reflection.name)
-
-
if target_association.respond_to?(:include?)
-
target_association.include?(record)
-
else
-
target_association == record
-
end
-
} || target.include?(record)
-
else
-
target.include?(record)
-
end
-
end
-
-
# If the :inverse_of option has been
-
# specified, then #find scans the entire collection.
-
1
def find_by_scan(*args)
-
expects_array = args.first.kind_of?(Array)
-
ids = args.flatten.compact.map{ |arg| arg.to_s }.uniq
-
-
if ids.size == 1
-
id = ids.first
-
record = load_target.detect { |r| id == r.id.to_s }
-
expects_array ? [ record ] : record
-
else
-
load_target.select { |r| ids.include?(r.id.to_s) }
-
end
-
end
-
-
# Fetches the first/last using SQL if possible, otherwise from the target array.
-
1
def first_nth_or_last(type, *args)
-
args.shift if args.first.is_a?(Hash) && args.first.empty?
-
-
collection = fetch_first_nth_or_last_using_find?(args) ? scope : load_target
-
collection.send(type, *args).tap do |record|
-
set_inverse_instance record if record.is_a? ActiveRecord::Base
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord::Associations
-
1
module ForeignAssociation
-
1
def foreign_key_present?
-
if reflection.klass.primary_key
-
owner.attribute_present?(reflection.active_record_primary_key)
-
else
-
false
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
# = Active Record Has Many Association
-
1
module Associations
-
# This is the proxy that handles a has many association.
-
#
-
# If the association has a <tt>:through</tt> option further specialization
-
# is provided by its child HasManyThroughAssociation.
-
1
class HasManyAssociation < CollectionAssociation #:nodoc:
-
1
include ForeignAssociation
-
-
1
def handle_dependency
-
case options[:dependent]
-
when :restrict_with_exception
-
raise ActiveRecord::DeleteRestrictionError.new(reflection.name) unless empty?
-
-
when :restrict_with_error
-
unless empty?
-
record = klass.human_attribute_name(reflection.name).downcase
-
owner.errors.add(:base, :"restrict_dependent_destroy.many", record: record)
-
false
-
end
-
-
else
-
if options[:dependent] == :destroy
-
# No point in executing the counter update since we're going to destroy the parent anyway
-
load_target.each { |t| t.destroyed_by_association = reflection }
-
destroy_all
-
else
-
delete_all
-
end
-
end
-
end
-
-
1
def insert_record(record, validate = true, raise = false)
-
9
set_owner_attributes(record)
-
9
set_inverse_instance(record)
-
-
9
if raise
-
record.save!(:validate => validate)
-
else
-
9
record.save(:validate => validate)
-
end
-
end
-
-
1
def empty?
-
if has_cached_counter?
-
size.zero?
-
else
-
super
-
end
-
end
-
-
1
private
-
-
# Returns the number of records in this collection.
-
#
-
# If the association has a counter cache it gets that value. Otherwise
-
# it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
-
# there's one. Some configuration options like :group make it impossible
-
# to do an SQL count, in those cases the array count will be used.
-
#
-
# That does not depend on whether the collection has already been loaded
-
# or not. The +size+ method is the one that takes the loaded flag into
-
# account and delegates to +count_records+ if needed.
-
#
-
# If the collection is empty the target is set to an empty array and
-
# the loaded flag is set to true as well.
-
1
def count_records
-
count = if has_cached_counter?
-
owner._read_attribute cached_counter_attribute_name
-
else
-
scope.count
-
end
-
-
# If there's nothing in the database and @target has no new records
-
# we are certain the current target is an empty array. This is a
-
# documented side-effect of the method that may avoid an extra SELECT.
-
@target ||= [] and loaded! if count == 0
-
-
[association_scope.limit_value, count].compact.min
-
end
-
-
1
def has_cached_counter?(reflection = reflection())
-
9
owner.attribute_present?(cached_counter_attribute_name(reflection))
-
end
-
-
1
def cached_counter_attribute_name(reflection = reflection())
-
18
if reflection.options[:counter_cache]
-
reflection.options[:counter_cache].to_s
-
else
-
18
"#{reflection.name}_count"
-
end
-
end
-
-
1
def update_counter(difference, reflection = reflection())
-
update_counter_in_database(difference, reflection)
-
update_counter_in_memory(difference, reflection)
-
end
-
-
1
def update_counter_in_database(difference, reflection = reflection())
-
if has_cached_counter?(reflection)
-
counter = cached_counter_attribute_name(reflection)
-
owner.class.update_counters(owner.id, counter => difference)
-
end
-
end
-
-
1
def update_counter_in_memory(difference, reflection = reflection())
-
9
if counter_must_be_updated_by_has_many?(reflection)
-
counter = cached_counter_attribute_name(reflection)
-
owner[counter] += difference
-
owner.send(:clear_attribute_changes, counter) # eww
-
end
-
end
-
-
# This shit is nasty. We need to avoid the following situation:
-
#
-
# * An associated record is deleted via record.destroy
-
# * Hence the callbacks run, and they find a belongs_to on the record with a
-
# :counter_cache options which points back at our owner. So they update the
-
# counter cache.
-
# * In which case, we must make sure to *not* update the counter cache, or else
-
# it will be decremented twice.
-
#
-
# Hence this method.
-
1
def inverse_which_updates_counter_cache(reflection = reflection())
-
9
counter_name = cached_counter_attribute_name(reflection)
-
9
inverse_which_updates_counter_named(counter_name, reflection)
-
end
-
1
alias inverse_updates_counter_cache? inverse_which_updates_counter_cache
-
-
1
def inverse_which_updates_counter_named(counter_name, reflection)
-
9
reflection.klass._reflections.values.find { |inverse_reflection|
-
inverse_reflection.belongs_to? &&
-
27
inverse_reflection.counter_cache_column == counter_name
-
}
-
end
-
1
alias inverse_updates_counter_named? inverse_which_updates_counter_named
-
-
1
def inverse_updates_counter_in_memory?(reflection)
-
9
inverse = inverse_which_updates_counter_cache(reflection)
-
9
inverse && inverse == reflection.inverse_of
-
end
-
-
1
def counter_must_be_updated_by_has_many?(reflection)
-
9
!inverse_updates_counter_in_memory?(reflection) && has_cached_counter?(reflection)
-
end
-
-
1
def delete_count(method, scope)
-
if method == :delete_all
-
scope.delete_all
-
else
-
scope.update_all(reflection.foreign_key => nil)
-
end
-
end
-
-
1
def delete_or_nullify_all_records(method)
-
count = delete_count(method, self.scope)
-
update_counter(-count)
-
end
-
-
# Deletes the records according to the <tt>:dependent</tt> option.
-
1
def delete_records(records, method)
-
if method == :destroy
-
records.each(&:destroy!)
-
update_counter(-records.length) unless inverse_updates_counter_cache?
-
else
-
scope = self.scope.where(reflection.klass.primary_key => records)
-
update_counter(-delete_count(method, scope))
-
end
-
end
-
-
1
def concat_records(records, *)
-
update_counter_if_success(super, records.length)
-
end
-
-
1
def _create_record(attributes, *)
-
9
if attributes.is_a?(Array)
-
super
-
else
-
9
update_counter_if_success(super, 1)
-
end
-
end
-
-
1
def update_counter_if_success(saved_successfully, difference)
-
9
if saved_successfully
-
9
update_counter_in_memory(difference)
-
end
-
9
saved_successfully
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
1
class JoinDependency # :nodoc:
-
1
autoload :JoinBase, 'active_record/associations/join_dependency/join_base'
-
1
autoload :JoinAssociation, 'active_record/associations/join_dependency/join_association'
-
-
1
class Aliases # :nodoc:
-
1
def initialize(tables)
-
@tables = tables
-
@alias_cache = tables.each_with_object({}) { |table,h|
-
h[table.node] = table.columns.each_with_object({}) { |column,i|
-
i[column.name] = column.alias
-
}
-
}
-
@name_and_alias_cache = tables.each_with_object({}) { |table,h|
-
h[table.node] = table.columns.map { |column|
-
[column.name, column.alias]
-
}
-
}
-
end
-
-
1
def columns
-
@tables.flat_map { |t| t.column_aliases }
-
end
-
-
# An array of [column_name, alias] pairs for the table
-
1
def column_aliases(node)
-
@name_and_alias_cache[node]
-
end
-
-
1
def column_alias(node, column)
-
@alias_cache[node][column]
-
end
-
-
1
class Table < Struct.new(:node, :columns)
-
1
def table
-
Arel::Nodes::TableAlias.new node.table, node.aliased_table_name
-
end
-
-
1
def column_aliases
-
t = table
-
columns.map { |column| t[column.name].as Arel.sql column.alias }
-
end
-
end
-
1
Column = Struct.new(:name, :alias)
-
end
-
-
1
attr_reader :alias_tracker, :base_klass, :join_root
-
-
1
def self.make_tree(associations)
-
42
hash = {}
-
42
walk_tree associations, hash
-
42
hash
-
end
-
-
1
def self.walk_tree(associations, hash)
-
42
case associations
-
when Symbol, String
-
hash[associations.to_sym] ||= {}
-
when Array
-
42
associations.each do |assoc|
-
walk_tree assoc, hash
-
end
-
when Hash
-
associations.each do |k,v|
-
cache = hash[k] ||= {}
-
walk_tree v, cache
-
end
-
else
-
raise ConfigurationError, associations.inspect
-
end
-
end
-
-
# base is the base class on which operation is taking place.
-
# associations is the list of associations which are joined using hash, symbol or array.
-
# joins is the list of all string join commands and arel nodes.
-
#
-
# Example :
-
#
-
# class Physician < ActiveRecord::Base
-
# has_many :appointments
-
# has_many :patients, through: :appointments
-
# end
-
#
-
# If I execute `@physician.patients.to_a` then
-
# base # => Physician
-
# associations # => []
-
# joins # => [#<Arel::Nodes::InnerJoin: ...]
-
#
-
# However if I execute `Physician.joins(:appointments).to_a` then
-
# base # => Physician
-
# associations # => [:appointments]
-
# joins # => []
-
#
-
1
def initialize(base, associations, joins)
-
42
@alias_tracker = AliasTracker.create(base.connection, joins)
-
42
@alias_tracker.aliased_table_for(base.table_name, base.table_name) # Updates the count for base.table_name to 1
-
42
tree = self.class.make_tree associations
-
42
@join_root = JoinBase.new base, build(tree, base)
-
42
@join_root.children.each { |child| construct_tables! @join_root, child }
-
end
-
-
1
def reflections
-
21
join_root.drop(1).map!(&:reflection)
-
end
-
-
1
def join_constraints(outer_joins)
-
21
joins = join_root.children.flat_map { |child|
-
make_inner_joins join_root, child
-
}
-
-
21
joins.concat outer_joins.flat_map { |oj|
-
21
if join_root.match? oj.join_root
-
21
walk join_root, oj.join_root
-
else
-
oj.join_root.children.flat_map { |child|
-
make_outer_joins oj.join_root, child
-
}
-
end
-
}
-
end
-
-
1
def aliases
-
Aliases.new join_root.each_with_index.map { |join_part,i|
-
columns = join_part.column_names.each_with_index.map { |column_name,j|
-
Aliases::Column.new column_name, "t#{i}_r#{j}"
-
}
-
Aliases::Table.new(join_part, columns)
-
}
-
end
-
-
1
def instantiate(result_set, aliases)
-
primary_key = aliases.column_alias(join_root, join_root.primary_key)
-
-
seen = Hash.new { |h,parent_klass|
-
h[parent_klass] = Hash.new { |i,parent_id|
-
i[parent_id] = Hash.new { |j,child_klass| j[child_klass] = {} }
-
}
-
}
-
-
model_cache = Hash.new { |h,klass| h[klass] = {} }
-
parents = model_cache[join_root]
-
column_aliases = aliases.column_aliases join_root
-
-
message_bus = ActiveSupport::Notifications.instrumenter
-
-
payload = {
-
record_count: result_set.length,
-
class_name: join_root.base_klass.name
-
}
-
-
message_bus.instrument('instantiation.active_record', payload) do
-
result_set.each { |row_hash|
-
parent = parents[row_hash[primary_key]] ||= join_root.instantiate(row_hash, column_aliases)
-
construct(parent, join_root, row_hash, result_set, seen, model_cache, aliases)
-
}
-
end
-
-
parents.values
-
end
-
-
1
private
-
-
1
def make_constraints(parent, child, tables, join_type)
-
chain = child.reflection.chain
-
foreign_table = parent.table
-
foreign_klass = parent.base_klass
-
child.join_constraints(foreign_table, foreign_klass, child, join_type, tables, child.reflection.scope_chain, chain)
-
end
-
-
1
def make_outer_joins(parent, child)
-
tables = table_aliases_for(parent, child)
-
join_type = Arel::Nodes::OuterJoin
-
info = make_constraints parent, child, tables, join_type
-
-
[info] + child.children.flat_map { |c| make_outer_joins(child, c) }
-
end
-
-
1
def make_inner_joins(parent, child)
-
tables = child.tables
-
join_type = Arel::Nodes::InnerJoin
-
info = make_constraints parent, child, tables, join_type
-
-
[info] + child.children.flat_map { |c| make_inner_joins(child, c) }
-
end
-
-
1
def table_aliases_for(parent, node)
-
node.reflection.chain.map { |reflection|
-
alias_tracker.aliased_table_for(
-
reflection.table_name,
-
table_alias_for(reflection, parent, reflection != node.reflection)
-
)
-
}
-
end
-
-
1
def construct_tables!(parent, node)
-
node.tables = table_aliases_for(parent, node)
-
node.children.each { |child| construct_tables! node, child }
-
end
-
-
1
def table_alias_for(reflection, parent, join)
-
name = "#{reflection.plural_name}_#{parent.table_name}"
-
name << "_join" if join
-
name
-
end
-
-
1
def walk(left, right)
-
21
intersection, missing = right.children.map { |node1|
-
[left.children.find { |node2| node1.match? node2 }, node1]
-
}.partition(&:first)
-
-
21
ojs = missing.flat_map { |_,n| make_outer_joins left, n }
-
21
intersection.flat_map { |l,r| walk l, r }.concat ojs
-
end
-
-
1
def find_reflection(klass, name)
-
klass._reflect_on_association(name) or
-
raise ConfigurationError, "Association named '#{ name }' was not found on #{ klass.name }; perhaps you misspelled it?"
-
end
-
-
1
def build(associations, base_klass)
-
42
associations.map do |name, right|
-
reflection = find_reflection base_klass, name
-
reflection.check_validity!
-
reflection.check_eager_loadable!
-
-
if reflection.polymorphic?
-
raise EagerLoadPolymorphicError.new(reflection)
-
end
-
-
JoinAssociation.new reflection, build(right, reflection.klass)
-
end
-
end
-
-
1
def construct(ar_parent, parent, row, rs, seen, model_cache, aliases)
-
return if ar_parent.nil?
-
primary_id = ar_parent.id
-
-
parent.children.each do |node|
-
if node.reflection.collection?
-
other = ar_parent.association(node.reflection.name)
-
other.loaded!
-
else
-
if ar_parent.association_cache.key?(node.reflection.name)
-
model = ar_parent.association(node.reflection.name).target
-
construct(model, node, row, rs, seen, model_cache, aliases)
-
next
-
end
-
end
-
-
key = aliases.column_alias(node, node.primary_key)
-
id = row[key]
-
if id.nil?
-
nil_association = ar_parent.association(node.reflection.name)
-
nil_association.loaded!
-
next
-
end
-
-
model = seen[parent.base_klass][primary_id][node.base_klass][id]
-
-
if model
-
construct(model, node, row, rs, seen, model_cache, aliases)
-
else
-
model = construct_model(ar_parent, node, row, model_cache, id, aliases)
-
seen[parent.base_klass][primary_id][node.base_klass][id] = model
-
construct(model, node, row, rs, seen, model_cache, aliases)
-
end
-
end
-
end
-
-
1
def construct_model(record, node, row, model_cache, id, aliases)
-
model = model_cache[node][id] ||= node.instantiate(row,
-
aliases.column_aliases(node))
-
other = record.association(node.reflection.name)
-
-
if node.reflection.collection?
-
other.target.push(model)
-
else
-
other.target = model
-
end
-
-
other.set_inverse_instance(model)
-
model
-
end
-
end
-
end
-
end
-
1
require 'active_record/associations/join_dependency/join_part'
-
-
1
module ActiveRecord
-
1
module Associations
-
1
class JoinDependency # :nodoc:
-
1
class JoinBase < JoinPart # :nodoc:
-
1
def match?(other)
-
21
return true if self == other
-
21
super && base_klass == other.base_klass
-
end
-
-
1
def table
-
base_klass.arel_table
-
end
-
-
1
def aliased_table_name
-
base_klass.table_name
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
1
class JoinDependency # :nodoc:
-
# A JoinPart represents a part of a JoinDependency. It is inherited
-
# by JoinBase and JoinAssociation. A JoinBase represents the Active Record which
-
# everything else is being joined onto. A JoinAssociation represents an association which
-
# is joining to the base. A JoinAssociation may result in more than one actual join
-
# operations (for example a has_and_belongs_to_many JoinAssociation would result in
-
# two; one for the join table and one for the target table).
-
1
class JoinPart # :nodoc:
-
1
include Enumerable
-
-
# The Active Record class which this join part is associated 'about'; for a JoinBase
-
# this is the actual base model, for a JoinAssociation this is the target model of the
-
# association.
-
1
attr_reader :base_klass, :children
-
-
1
delegate :table_name, :column_names, :primary_key, :to => :base_klass
-
-
1
def initialize(base_klass, children)
-
42
@base_klass = base_klass
-
42
@children = children
-
end
-
-
1
def name
-
reflection.name
-
end
-
-
1
def match?(other)
-
21
self.class == other.class
-
end
-
-
1
def each(&block)
-
21
yield self
-
21
children.each { |child| child.each(&block) }
-
end
-
-
# An Arel::Table for the active_record
-
1
def table
-
raise NotImplementedError
-
end
-
-
# The alias for the active_record's table
-
1
def aliased_table_name
-
raise NotImplementedError
-
end
-
-
1
def extract_record(row, column_names_with_alias)
-
# This code is performance critical as it is called per row.
-
# see: https://github.com/rails/rails/pull/12185
-
hash = {}
-
-
index = 0
-
length = column_names_with_alias.length
-
-
while index < length
-
column_name, alias_name = column_names_with_alias[index]
-
hash[column_name] = row[alias_name]
-
index += 1
-
end
-
-
hash
-
end
-
-
1
def instantiate(row, aliases)
-
base_klass.instantiate(extract_record(row, aliases))
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
# Implements the details of eager loading of Active Record associations.
-
#
-
# Suppose that you have the following two Active Record models:
-
#
-
# class Author < ActiveRecord::Base
-
# # columns: name, age
-
# has_many :books
-
# end
-
#
-
# class Book < ActiveRecord::Base
-
# # columns: title, sales
-
# end
-
#
-
# When you load an author with all associated books Active Record will make
-
# multiple queries like this:
-
#
-
# Author.includes(:books).where(:name => ['bell hooks', 'Homer').to_a
-
#
-
# => SELECT `authors`.* FROM `authors` WHERE `name` IN ('bell hooks', 'Homer')
-
# => SELECT `books`.* FROM `books` WHERE `author_id` IN (2, 5)
-
#
-
# Active Record saves the ids of the records from the first query to use in
-
# the second. Depending on the number of associations involved there can be
-
# arbitrarily many SQL queries made.
-
#
-
# However, if there is a WHERE clause that spans across tables Active
-
# Record will fall back to a slightly more resource-intensive single query:
-
#
-
# Author.includes(:books).where(books: {title: 'Illiad'}).to_a
-
# => SELECT `authors`.`id` AS t0_r0, `authors`.`name` AS t0_r1, `authors`.`age` AS t0_r2,
-
# `books`.`id` AS t1_r0, `books`.`title` AS t1_r1, `books`.`sales` AS t1_r2
-
# FROM `authors`
-
# LEFT OUTER JOIN `books` ON `authors`.`id` = `books`.`author_id`
-
# WHERE `books`.`title` = 'Illiad'
-
#
-
# This could result in many rows that contain redundant data and it performs poorly at scale
-
# and is therefore only used when necessary.
-
#
-
1
class Preloader #:nodoc:
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :Association, 'active_record/associations/preloader/association'
-
1
autoload :SingularAssociation, 'active_record/associations/preloader/singular_association'
-
1
autoload :CollectionAssociation, 'active_record/associations/preloader/collection_association'
-
1
autoload :ThroughAssociation, 'active_record/associations/preloader/through_association'
-
-
1
autoload :HasMany, 'active_record/associations/preloader/has_many'
-
1
autoload :HasManyThrough, 'active_record/associations/preloader/has_many_through'
-
1
autoload :HasOne, 'active_record/associations/preloader/has_one'
-
1
autoload :HasOneThrough, 'active_record/associations/preloader/has_one_through'
-
1
autoload :BelongsTo, 'active_record/associations/preloader/belongs_to'
-
end
-
-
# Eager loads the named associations for the given Active Record record(s).
-
#
-
# In this description, 'association name' shall refer to the name passed
-
# to an association creation method. For example, a model that specifies
-
# <tt>belongs_to :author</tt>, <tt>has_many :buyers</tt> has association
-
# names +:author+ and +:buyers+.
-
#
-
# == Parameters
-
# +records+ is an array of ActiveRecord::Base. This array needs not be flat,
-
# i.e. +records+ itself may also contain arrays of records. In any case,
-
# +preload_associations+ will preload the all associations records by
-
# flattening +records+.
-
#
-
# +associations+ specifies one or more associations that you want to
-
# preload. It may be:
-
# - a Symbol or a String which specifies a single association name. For
-
# example, specifying +:books+ allows this method to preload all books
-
# for an Author.
-
# - an Array which specifies multiple association names. This array
-
# is processed recursively. For example, specifying <tt>[:avatar, :books]</tt>
-
# allows this method to preload an author's avatar as well as all of his
-
# books.
-
# - a Hash which specifies multiple association names, as well as
-
# association names for the to-be-preloaded association objects. For
-
# example, specifying <tt>{ author: :avatar }</tt> will preload a
-
# book's author, as well as that author's avatar.
-
#
-
# +:associations+ has the same format as the +:include+ option for
-
# <tt>ActiveRecord::Base.find</tt>. So +associations+ could look like this:
-
#
-
# :books
-
# [ :books, :author ]
-
# { author: :avatar }
-
# [ :books, { author: :avatar } ]
-
-
1
NULL_RELATION = Struct.new(:values, :bind_values).new({}, [])
-
-
1
def preload(records, associations, preload_scope = nil)
-
records = Array.wrap(records).compact.uniq
-
associations = Array.wrap(associations)
-
preload_scope = preload_scope || NULL_RELATION
-
-
if records.empty?
-
[]
-
else
-
associations.flat_map { |association|
-
preloaders_on association, records, preload_scope
-
}
-
end
-
end
-
-
1
private
-
-
1
def preloaders_on(association, records, scope)
-
case association
-
when Hash
-
preloaders_for_hash(association, records, scope)
-
when Symbol
-
preloaders_for_one(association, records, scope)
-
when String
-
preloaders_for_one(association.to_sym, records, scope)
-
else
-
raise ArgumentError, "#{association.inspect} was not recognised for preload"
-
end
-
end
-
-
1
def preloaders_for_hash(association, records, scope)
-
association.flat_map { |parent, child|
-
loaders = preloaders_for_one parent, records, scope
-
-
recs = loaders.flat_map(&:preloaded_records).uniq
-
loaders.concat Array.wrap(child).flat_map { |assoc|
-
preloaders_on assoc, recs, scope
-
}
-
loaders
-
}
-
end
-
-
# Not all records have the same class, so group then preload group on the reflection
-
# itself so that if various subclass share the same association then we do not split
-
# them unnecessarily
-
#
-
# Additionally, polymorphic belongs_to associations can have multiple associated
-
# classes, depending on the polymorphic_type field. So we group by the classes as
-
# well.
-
1
def preloaders_for_one(association, records, scope)
-
grouped_records(association, records).flat_map do |reflection, klasses|
-
klasses.map do |rhs_klass, rs|
-
loader = preloader_for(reflection, rs, rhs_klass).new(rhs_klass, rs, reflection, scope)
-
loader.run self
-
loader
-
end
-
end
-
end
-
-
1
def grouped_records(association, records)
-
h = {}
-
records.each do |record|
-
next unless record
-
assoc = record.association(association)
-
klasses = h[assoc.reflection] ||= {}
-
(klasses[assoc.klass] ||= []) << record
-
end
-
h
-
end
-
-
1
class AlreadyLoaded
-
1
attr_reader :owners, :reflection
-
-
1
def initialize(klass, owners, reflection, preload_scope)
-
@owners = owners
-
@reflection = reflection
-
end
-
-
1
def run(preloader); end
-
-
1
def preloaded_records
-
owners.flat_map { |owner| owner.association(reflection.name).target }
-
end
-
end
-
-
1
class NullPreloader
-
1
def self.new(klass, owners, reflection, preload_scope); self; end
-
1
def self.run(preloader); end
-
1
def self.preloaded_records; []; end
-
end
-
-
1
def preloader_for(reflection, owners, rhs_klass)
-
return NullPreloader unless rhs_klass
-
-
if owners.first.association(reflection.name).loaded?
-
return AlreadyLoaded
-
end
-
reflection.check_preloadable!
-
-
case reflection.macro
-
when :has_many
-
reflection.options[:through] ? HasManyThrough : HasMany
-
when :has_one
-
reflection.options[:through] ? HasOneThrough : HasOne
-
when :belongs_to
-
BelongsTo
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module Associations
-
1
class SingularAssociation < Association #:nodoc:
-
# Implements the reader method, e.g. foo.bar for Foo.has_one :bar
-
1
def reader(force_reload = false)
-
54
if force_reload
-
klass.uncached { reload }
-
elsif !loaded? || stale_target?
-
12
reload
-
end
-
-
54
target
-
end
-
-
# Implements the writer method, e.g. foo.bar= for Foo.belongs_to :bar
-
1
def writer(record)
-
40
replace(record)
-
end
-
-
1
def create(attributes = {}, &block)
-
_create_record(attributes, &block)
-
end
-
-
1
def create!(attributes = {}, &block)
-
_create_record(attributes, true, &block)
-
end
-
-
1
def build(attributes = {})
-
record = build_record(attributes)
-
yield(record) if block_given?
-
set_new_record(record)
-
record
-
end
-
-
1
private
-
-
1
def create_scope
-
scope.scope_for_create.stringify_keys.except(klass.primary_key)
-
end
-
-
1
def get_records
-
if reflection.scope_chain.any?(&:any?) ||
-
12
scope.eager_loading? ||
-
klass.current_scope ||
-
klass.default_scopes.any?
-
-
return scope.limit(1).to_a
-
end
-
-
12
conn = klass.connection
-
12
sc = reflection.association_scope_cache(conn, owner) do
-
3
StatementCache.create(conn) { |params|
-
6
as = AssociationScope.create { params.bind }
-
3
target_scope.merge(as.scope(self, conn)).limit(1)
-
}
-
end
-
-
12
binds = AssociationScope.get_bind_values(owner, reflection.chain)
-
12
sc.execute binds, klass, klass.connection
-
end
-
-
1
def find_target
-
12
if record = get_records.first
-
12
set_inverse_instance record
-
end
-
end
-
-
1
def replace(record)
-
raise NotImplementedError, "Subclasses must implement a replace(record) method"
-
end
-
-
1
def set_new_record(record)
-
replace(record)
-
end
-
-
1
def _create_record(attributes, raise_error = false)
-
record = build_record(attributes)
-
yield(record) if block_given?
-
saved = record.save
-
set_new_record(record)
-
raise RecordInvalid.new(record) if !saved && raise_error
-
record
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
class Attribute # :nodoc:
-
1
class << self
-
1
def from_database(name, value, type)
-
1142
FromDatabase.new(name, value, type)
-
end
-
-
1
def from_user(name, value, type)
-
693
FromUser.new(name, value, type)
-
end
-
-
1
def with_cast_value(name, value, type)
-
WithCastValue.new(name, value, type)
-
end
-
-
1
def null(name)
-
9
Null.new(name)
-
end
-
-
1
def uninitialized(name, type)
-
Uninitialized.new(name, type)
-
end
-
end
-
-
1
attr_reader :name, :value_before_type_cast, :type
-
-
# This method should not be called directly.
-
# Use #from_database or #from_user
-
1
def initialize(name, value_before_type_cast, type)
-
1844
@name = name
-
1844
@value_before_type_cast = value_before_type_cast
-
1844
@type = type
-
end
-
-
1
def value
-
# `defined?` is cheaper than `||=` when we get back falsy values
-
8735
@value = original_value unless defined?(@value)
-
8735
@value
-
end
-
-
1
def original_value
-
1835
type_cast(value_before_type_cast)
-
end
-
-
1
def value_for_database
-
1612
type.type_cast_for_database(value)
-
end
-
-
1
def changed_from?(old_value)
-
693
type.changed?(old_value, value, value_before_type_cast)
-
end
-
-
1
def changed_in_place_from?(old_value)
-
4960
has_been_read? && type.changed_in_place?(old_value, value)
-
end
-
-
1
def with_value_from_user(value)
-
693
self.class.from_user(name, value, type)
-
end
-
-
1
def with_value_from_database(value)
-
self.class.from_database(name, value, type)
-
end
-
-
1
def with_cast_value(value)
-
self.class.with_cast_value(name, value, type)
-
end
-
-
1
def type_cast(*)
-
raise NotImplementedError
-
end
-
-
1
def initialized?
-
167
true
-
end
-
-
1
def came_from_user?
-
false
-
end
-
-
1
def ==(other)
-
self.class == other.class &&
-
name == other.name &&
-
value_before_type_cast == other.value_before_type_cast &&
-
type == other.type
-
end
-
-
1
protected
-
-
1
def initialize_dup(other)
-
if defined?(@value) && @value.duplicable?
-
@value = @value.dup
-
end
-
end
-
-
1
private
-
-
1
def has_been_read?
-
4960
defined?(@value)
-
end
-
-
1
class FromDatabase < Attribute # :nodoc:
-
1
def type_cast(value)
-
1142
type.type_cast_from_database(value)
-
end
-
end
-
-
1
class FromUser < Attribute # :nodoc:
-
1
def type_cast(value)
-
693
type.type_cast_from_user(value)
-
end
-
-
1
def came_from_user?
-
true
-
end
-
end
-
-
1
class WithCastValue < Attribute # :nodoc:
-
1
def type_cast(value)
-
value
-
end
-
-
1
def changed_in_place_from?(old_value)
-
false
-
end
-
end
-
-
1
class Null < Attribute # :nodoc:
-
1
def initialize(name)
-
9
super(name, nil, Type::Value.new)
-
end
-
-
1
def value
-
nil
-
end
-
-
1
def with_value_from_database(value)
-
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
-
end
-
1
alias_method :with_value_from_user, :with_value_from_database
-
end
-
-
1
class Uninitialized < Attribute # :nodoc:
-
1
def initialize(name, type)
-
super(name, nil, type)
-
end
-
-
1
def value
-
if block_given?
-
yield name
-
end
-
end
-
-
1
def value_for_database
-
end
-
-
1
def initialized?
-
false
-
end
-
end
-
1
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized
-
end
-
end
-
1
require 'yaml'
-
-
1
module ActiveRecord
-
1
module Coders # :nodoc:
-
1
class YAMLColumn # :nodoc:
-
-
1
attr_accessor :object_class
-
-
1
def initialize(object_class = Object)
-
1
@object_class = object_class
-
end
-
-
1
def dump(obj)
-
33
return if obj.nil?
-
-
33
unless obj.is_a?(object_class)
-
raise SerializationTypeMismatch,
-
"Attribute was supposed to be a #{object_class}, but was a #{obj.class}. -- #{obj.inspect}"
-
end
-
33
YAML.dump obj
-
end
-
-
1
def load(yaml)
-
129
return object_class.new if object_class != Object && yaml.nil?
-
3
return yaml unless yaml.is_a?(String) && yaml =~ /^---/
-
3
obj = YAML.load(yaml)
-
-
3
unless obj.is_a?(object_class) || obj.nil?
-
raise SerializationTypeMismatch,
-
"Attribute was supposed to be a #{object_class}, but was a #{obj.class}"
-
end
-
3
obj ||= object_class.new if object_class != Object
-
-
3
obj
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
module ConnectionAdapters
-
1
class TransactionState
-
1
attr_reader :parent
-
-
1
VALID_STATES = Set.new([:committed, :rolledback, nil])
-
-
1
def initialize(state = nil)
-
62
@state = state
-
62
@parent = nil
-
end
-
-
1
def finalized?
-
286
@state
-
end
-
-
1
def committed?
-
@state == :committed
-
end
-
-
1
def rolledback?
-
50
@state == :rolledback
-
end
-
-
1
def completed?
-
committed? || rolledback?
-
end
-
-
1
def set_state(state)
-
62
if !VALID_STATES.include?(state)
-
raise ArgumentError, "Invalid transaction state: #{state}"
-
end
-
62
@state = state
-
end
-
end
-
-
1
class NullTransaction #:nodoc:
-
1
def initialize; end
-
1
def closed?; true; end
-
1
def open?; false; end
-
1
def joinable?; false; end
-
1
def add_record(record); end
-
end
-
-
1
class Transaction #:nodoc:
-
-
1
attr_reader :connection, :state, :records, :savepoint_name
-
1
attr_writer :joinable
-
-
1
def initialize(connection, options)
-
62
@connection = connection
-
62
@state = TransactionState.new
-
62
@records = []
-
62
@joinable = options.fetch(:joinable, true)
-
end
-
-
1
def add_record(record)
-
records << record
-
end
-
-
1
def rollback
-
7
@state.set_state(:rolledback)
-
end
-
-
1
def rollback_records
-
7
ite = records.uniq
-
7
while record = ite.shift
-
begin
-
record.rolledback! full_rollback?
-
rescue => e
-
raise if ActiveRecord::Base.raise_in_transactional_callbacks
-
record.logger.error(e) if record.respond_to?(:logger) && record.logger
-
end
-
end
-
ensure
-
7
ite.each do |i|
-
i.rolledback!(full_rollback?, false)
-
end
-
end
-
-
1
def commit
-
55
@state.set_state(:committed)
-
end
-
-
1
def commit_records
-
ite = records.uniq
-
while record = ite.shift
-
begin
-
record.committed!
-
rescue => e
-
raise if ActiveRecord::Base.raise_in_transactional_callbacks
-
record.logger.error(e) if record.respond_to?(:logger) && record.logger
-
end
-
end
-
ensure
-
ite.each do |i|
-
i.committed!(false)
-
end
-
end
-
-
1
def full_rollback?; true; end
-
78
def joinable?; @joinable; end
-
8
def closed?; false; end
-
8
def open?; !closed?; end
-
end
-
-
1
class SavepointTransaction < Transaction
-
-
1
def initialize(connection, savepoint_name, options)
-
55
super(connection, options)
-
55
if options[:isolation]
-
raise ActiveRecord::TransactionIsolationError, "cannot set transaction isolation in a nested transaction"
-
end
-
55
connection.create_savepoint(@savepoint_name = savepoint_name)
-
end
-
-
1
def rollback
-
connection.rollback_to_savepoint(savepoint_name)
-
super
-
rollback_records
-
end
-
-
1
def commit
-
55
connection.release_savepoint(savepoint_name)
-
55
super
-
55
parent = connection.transaction_manager.current_transaction
-
55
records.each { |r| parent.add_record(r) }
-
end
-
-
1
def full_rollback?; false; end
-
end
-
-
1
class RealTransaction < Transaction
-
-
1
def initialize(connection, options)
-
7
super
-
7
if options[:isolation]
-
connection.begin_isolated_db_transaction(options[:isolation])
-
else
-
7
connection.begin_db_transaction
-
end
-
end
-
-
1
def rollback
-
7
connection.rollback_db_transaction
-
7
super
-
7
rollback_records
-
end
-
-
1
def commit
-
connection.commit_db_transaction
-
super
-
commit_records
-
end
-
end
-
-
1
class TransactionManager #:nodoc:
-
1
def initialize(connection)
-
1
@stack = []
-
1
@connection = connection
-
end
-
-
1
def begin_transaction(options = {})
-
62
transaction =
-
if @stack.empty?
-
7
RealTransaction.new(@connection, options)
-
else
-
55
SavepointTransaction.new(@connection, "active_record_#{@stack.size}", options)
-
end
-
62
@stack.push(transaction)
-
62
transaction
-
end
-
-
1
def commit_transaction
-
55
@stack.pop.commit
-
end
-
-
1
def rollback_transaction
-
7
@stack.pop.rollback
-
end
-
-
1
def within_new_transaction(options = {})
-
55
transaction = begin_transaction options
-
55
yield
-
rescue Exception => error
-
rollback_transaction if transaction
-
raise
-
ensure
-
55
unless error
-
55
if Thread.current.status == 'aborting'
-
rollback_transaction if transaction
-
else
-
55
begin
-
55
commit_transaction
-
rescue Exception
-
transaction.rollback unless transaction.state.completed?
-
raise
-
end
-
end
-
end
-
end
-
-
1
def open_transactions
-
@stack.size
-
end
-
-
1
def current_transaction
-
207
@stack.last || NULL_TRANSACTION
-
end
-
-
1
private
-
1
NULL_TRANSACTION = NullTransaction.new
-
end
-
end
-
end
-
1
require 'erb'
-
1
require 'yaml'
-
-
1
module ActiveRecord
-
1
class FixtureSet
-
1
class File # :nodoc:
-
1
include Enumerable
-
-
##
-
# Open a fixture file named +file+. When called with a block, the block
-
# is called with the filehandle and the filehandle is automatically closed
-
# when the block finishes.
-
1
def self.open(file)
-
x = new file
-
block_given? ? yield(x) : x
-
end
-
-
1
def initialize(file)
-
@file = file
-
@rows = nil
-
end
-
-
1
def each(&block)
-
rows.each(&block)
-
end
-
-
-
1
private
-
1
def rows
-
return @rows if @rows
-
-
begin
-
data = YAML.load(render(IO.read(@file)))
-
rescue ArgumentError, Psych::SyntaxError => error
-
raise Fixture::FormatError, "a YAML error occurred parsing #{@file}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{error.class}: #{error}", error.backtrace
-
end
-
@rows = data ? validate(data).to_a : []
-
end
-
-
1
def render(content)
-
context = ActiveRecord::FixtureSet::RenderContext.create_subclass.new
-
ERB.new(content).result(context.get_binding)
-
end
-
-
# Validate our unmarshalled data.
-
1
def validate(data)
-
unless Hash === data || YAML::Omap === data
-
raise Fixture::FormatError, 'fixture is not a hash'
-
end
-
-
raise Fixture::FormatError unless data.all? { |name, row| Hash === row }
-
data
-
end
-
end
-
end
-
end
-
1
require 'erb'
-
1
require 'yaml'
-
1
require 'zlib'
-
1
require 'active_support/dependencies'
-
1
require 'active_support/core_ext/digest/uuid'
-
1
require 'active_record/fixture_set/file'
-
1
require 'active_record/errors'
-
-
1
module ActiveRecord
-
1
class FixtureClassNotFound < ActiveRecord::ActiveRecordError #:nodoc:
-
end
-
-
# \Fixtures are a way of organizing data that you want to test against; in short, sample data.
-
#
-
# They are stored in YAML files, one file per model, which are placed in the directory
-
# appointed by <tt>ActiveSupport::TestCase.fixture_path=(path)</tt> (this is automatically
-
# configured for Rails, so you can just put your files in <tt><your-rails-app>/test/fixtures/</tt>).
-
# The fixture file ends with the +.yml+ file extension, for example:
-
# <tt><your-rails-app>/test/fixtures/web_sites.yml</tt>).
-
#
-
# The format of a fixture file looks like this:
-
#
-
# rubyonrails:
-
# id: 1
-
# name: Ruby on Rails
-
# url: http://www.rubyonrails.org
-
#
-
# google:
-
# id: 2
-
# name: Google
-
# url: http://www.google.com
-
#
-
# This fixture file includes two fixtures. Each YAML fixture (ie. record) is given a name and
-
# is followed by an indented list of key/value pairs in the "key: value" format. Records are
-
# separated by a blank line for your viewing pleasure.
-
#
-
# Note: Fixtures are unordered. If you want ordered fixtures, use the omap YAML type.
-
# See http://yaml.org/type/omap.html
-
# for the specification. You will need ordered fixtures when you have foreign key constraints
-
# on keys in the same table. This is commonly needed for tree structures. Example:
-
#
-
# --- !omap
-
# - parent:
-
# id: 1
-
# parent_id: NULL
-
# title: Parent
-
# - child:
-
# id: 2
-
# parent_id: 1
-
# title: Child
-
#
-
# = Using Fixtures in Test Cases
-
#
-
# Since fixtures are a testing construct, we use them in our unit and functional tests. There
-
# are two ways to use the fixtures, but first let's take a look at a sample unit test:
-
#
-
# require 'test_helper'
-
#
-
# class WebSiteTest < ActiveSupport::TestCase
-
# test "web_site_count" do
-
# assert_equal 2, WebSite.count
-
# end
-
# end
-
#
-
# By default, +test_helper.rb+ will load all of your fixtures into your test
-
# database, so this test will succeed.
-
#
-
# The testing environment will automatically load the all fixtures into the database before each
-
# test. To ensure consistent data, the environment deletes the fixtures before running the load.
-
#
-
# In addition to being available in the database, the fixture's data may also be accessed by
-
# using a special dynamic method, which has the same name as the model, and accepts the
-
# name of the fixture to instantiate:
-
#
-
# test "find" do
-
# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
-
# end
-
#
-
# Alternatively, you may enable auto-instantiation of the fixture data. For instance, take the
-
# following tests:
-
#
-
# test "find_alt_method_1" do
-
# assert_equal "Ruby on Rails", @web_sites['rubyonrails']['name']
-
# end
-
#
-
# test "find_alt_method_2" do
-
# assert_equal "Ruby on Rails", @rubyonrails.name
-
# end
-
#
-
# In order to use these methods to access fixtured data within your testcases, you must specify one of the
-
# following in your <tt>ActiveSupport::TestCase</tt>-derived class:
-
#
-
# - to fully enable instantiated fixtures (enable alternate methods #1 and #2 above)
-
# self.use_instantiated_fixtures = true
-
#
-
# - create only the hash for the fixtures, do not 'find' each instance (enable alternate method #1 only)
-
# self.use_instantiated_fixtures = :no_instances
-
#
-
# Using either of these alternate methods incurs a performance hit, as the fixtured data must be fully
-
# traversed in the database to create the fixture hash and/or instance variables. This is expensive for
-
# large sets of fixtured data.
-
#
-
# = Dynamic fixtures with ERB
-
#
-
# Some times you don't care about the content of the fixtures as much as you care about the volume.
-
# In these cases, you can mix ERB in with your YAML fixtures to create a bunch of fixtures for load
-
# testing, like:
-
#
-
# <% 1.upto(1000) do |i| %>
-
# fix_<%= i %>:
-
# id: <%= i %>
-
# name: guy_<%= 1 %>
-
# <% end %>
-
#
-
# This will create 1000 very simple fixtures.
-
#
-
# Using ERB, you can also inject dynamic values into your fixtures with inserts like
-
# <tt><%= Date.today.strftime("%Y-%m-%d") %></tt>.
-
# This is however a feature to be used with some caution. The point of fixtures are that they're
-
# stable units of predictable sample data. If you feel that you need to inject dynamic values, then
-
# perhaps you should reexamine whether your application is properly testable. Hence, dynamic values
-
# in fixtures are to be considered a code smell.
-
#
-
# Helper methods defined in a fixture will not be available in other fixtures, to prevent against
-
# unwanted inter-test dependencies. Methods used by multiple fixtures should be defined in a module
-
# that is included in <tt>ActiveRecord::FixtureSet.context_class</tt>.
-
#
-
# - define a helper method in `test_helper.rb`
-
# module FixtureFileHelpers
-
# def file_sha(path)
-
# Digest::SHA2.hexdigest(File.read(Rails.root.join('test/fixtures', path)))
-
# end
-
# end
-
# ActiveRecord::FixtureSet.context_class.send :include, FixtureFileHelpers
-
#
-
# - use the helper method in a fixture
-
# photo:
-
# name: kitten.png
-
# sha: <%= file_sha 'files/kitten.png' %>
-
#
-
# = Transactional Fixtures
-
#
-
# Test cases can use begin+rollback to isolate their changes to the database instead of having to
-
# delete+insert for every test case.
-
#
-
# class FooTest < ActiveSupport::TestCase
-
# self.use_transactional_fixtures = true
-
#
-
# test "godzilla" do
-
# assert !Foo.all.empty?
-
# Foo.destroy_all
-
# assert Foo.all.empty?
-
# end
-
#
-
# test "godzilla aftermath" do
-
# assert !Foo.all.empty?
-
# end
-
# end
-
#
-
# If you preload your test database with all fixture data (probably in the rake task) and use
-
# transactional fixtures, then you may omit all fixtures declarations in your test cases since
-
# all the data's already there and every case rolls back its changes.
-
#
-
# In order to use instantiated fixtures with preloaded data, set +self.pre_loaded_fixtures+ to
-
# true. This will provide access to fixture data for every table that has been loaded through
-
# fixtures (depending on the value of +use_instantiated_fixtures+).
-
#
-
# When *not* to use transactional fixtures:
-
#
-
# 1. You're testing whether a transaction works correctly. Nested transactions don't commit until
-
# all parent transactions commit, particularly, the fixtures transaction which is begun in setup
-
# and rolled back in teardown. Thus, you won't be able to verify
-
# the results of your transaction until Active Record supports nested transactions or savepoints (in progress).
-
# 2. Your database does not support transactions. Every Active Record database supports transactions except MySQL MyISAM.
-
# Use InnoDB, MaxDB, or NDB instead.
-
#
-
# = Advanced Fixtures
-
#
-
# Fixtures that don't specify an ID get some extra features:
-
#
-
# * Stable, autogenerated IDs
-
# * Label references for associations (belongs_to, has_one, has_many)
-
# * HABTM associations as inline lists
-
#
-
# There are some more advanced features available even if the id is specified:
-
#
-
# * Autofilled timestamp columns
-
# * Fixture label interpolation
-
# * Support for YAML defaults
-
#
-
# == Stable, Autogenerated IDs
-
#
-
# Here, have a monkey fixture:
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
#
-
# reginald:
-
# id: 2
-
# name: Reginald the Pirate
-
#
-
# Each of these fixtures has two unique identifiers: one for the database
-
# and one for the humans. Why don't we generate the primary key instead?
-
# Hashing each fixture's label yields a consistent ID:
-
#
-
# george: # generated id: 503576764
-
# name: George the Monkey
-
#
-
# reginald: # generated id: 324201669
-
# name: Reginald the Pirate
-
#
-
# Active Record looks at the fixture's model class, discovers the correct
-
# primary key, and generates it right before inserting the fixture
-
# into the database.
-
#
-
# The generated ID for a given label is constant, so we can discover
-
# any fixture's ID without loading anything, as long as we know the label.
-
#
-
# == Label references for associations (belongs_to, has_one, has_many)
-
#
-
# Specifying foreign keys in fixtures can be very fragile, not to
-
# mention difficult to read. Since Active Record can figure out the ID of
-
# any fixture from its label, you can specify FK's by label instead of ID.
-
#
-
# === belongs_to
-
#
-
# Let's break out some more monkeys and pirates.
-
#
-
# ### in pirates.yml
-
#
-
# reginald:
-
# id: 1
-
# name: Reginald the Pirate
-
# monkey_id: 1
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
# pirate_id: 1
-
#
-
# Add a few more monkeys and pirates and break this into multiple files,
-
# and it gets pretty hard to keep track of what's going on. Let's
-
# use labels instead of IDs:
-
#
-
# ### in pirates.yml
-
#
-
# reginald:
-
# name: Reginald the Pirate
-
# monkey: george
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# name: George the Monkey
-
# pirate: reginald
-
#
-
# Pow! All is made clear. Active Record reflects on the fixture's model class,
-
# finds all the +belongs_to+ associations, and allows you to specify
-
# a target *label* for the *association* (monkey: george) rather than
-
# a target *id* for the *FK* (<tt>monkey_id: 1</tt>).
-
#
-
# ==== Polymorphic belongs_to
-
#
-
# Supporting polymorphic relationships is a little bit more complicated, since
-
# Active Record needs to know what type your association is pointing at. Something
-
# like this should look familiar:
-
#
-
# ### in fruit.rb
-
#
-
# belongs_to :eater, polymorphic: true
-
#
-
# ### in fruits.yml
-
#
-
# apple:
-
# id: 1
-
# name: apple
-
# eater_id: 1
-
# eater_type: Monkey
-
#
-
# Can we do better? You bet!
-
#
-
# apple:
-
# eater: george (Monkey)
-
#
-
# Just provide the polymorphic target type and Active Record will take care of the rest.
-
#
-
# === has_and_belongs_to_many
-
#
-
# Time to give our monkey some fruit.
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
#
-
# ### in fruits.yml
-
#
-
# apple:
-
# id: 1
-
# name: apple
-
#
-
# orange:
-
# id: 2
-
# name: orange
-
#
-
# grape:
-
# id: 3
-
# name: grape
-
#
-
# ### in fruits_monkeys.yml
-
#
-
# apple_george:
-
# fruit_id: 1
-
# monkey_id: 1
-
#
-
# orange_george:
-
# fruit_id: 2
-
# monkey_id: 1
-
#
-
# grape_george:
-
# fruit_id: 3
-
# monkey_id: 1
-
#
-
# Let's make the HABTM fixture go away.
-
#
-
# ### in monkeys.yml
-
#
-
# george:
-
# id: 1
-
# name: George the Monkey
-
# fruits: apple, orange, grape
-
#
-
# ### in fruits.yml
-
#
-
# apple:
-
# name: apple
-
#
-
# orange:
-
# name: orange
-
#
-
# grape:
-
# name: grape
-
#
-
# Zap! No more fruits_monkeys.yml file. We've specified the list of fruits
-
# on George's fixture, but we could've just as easily specified a list
-
# of monkeys on each fruit. As with +belongs_to+, Active Record reflects on
-
# the fixture's model class and discovers the +has_and_belongs_to_many+
-
# associations.
-
#
-
# == Autofilled Timestamp Columns
-
#
-
# If your table/model specifies any of Active Record's
-
# standard timestamp columns (+created_at+, +created_on+, +updated_at+, +updated_on+),
-
# they will automatically be set to <tt>Time.now</tt>.
-
#
-
# If you've set specific values, they'll be left alone.
-
#
-
# == Fixture label interpolation
-
#
-
# The label of the current fixture is always available as a column value:
-
#
-
# geeksomnia:
-
# name: Geeksomnia's Account
-
# subdomain: $LABEL
-
# email: $LABEL@email.com
-
#
-
# Also, sometimes (like when porting older join table fixtures) you'll need
-
# to be able to get a hold of the identifier for a given label. ERB
-
# to the rescue:
-
#
-
# george_reginald:
-
# monkey_id: <%= ActiveRecord::FixtureSet.identify(:reginald) %>
-
# pirate_id: <%= ActiveRecord::FixtureSet.identify(:george) %>
-
#
-
# == Support for YAML defaults
-
#
-
# You can set and reuse defaults in your fixtures YAML file.
-
# This is the same technique used in the +database.yml+ file to specify
-
# defaults:
-
#
-
# DEFAULTS: &DEFAULTS
-
# created_on: <%= 3.weeks.ago.to_s(:db) %>
-
#
-
# first:
-
# name: Smurf
-
# <<: *DEFAULTS
-
#
-
# second:
-
# name: Fraggle
-
# <<: *DEFAULTS
-
#
-
# Any fixture labeled "DEFAULTS" is safely ignored.
-
1
class FixtureSet
-
#--
-
# An instance of FixtureSet is normally stored in a single YAML file and
-
# possibly in a folder with the same name.
-
#++
-
-
1
MAX_ID = 2 ** 30 - 1
-
-
2
@@all_cached_fixtures = Hash.new { |h,k| h[k] = {} }
-
-
1
def self.default_fixture_model_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
-
config.pluralize_table_names ?
-
fixture_set_name.singularize.camelize :
-
fixture_set_name.camelize
-
end
-
-
1
def self.default_fixture_table_name(fixture_set_name, config = ActiveRecord::Base) # :nodoc:
-
"#{ config.table_name_prefix }"\
-
"#{ fixture_set_name.tr('/', '_') }"\
-
"#{ config.table_name_suffix }".to_sym
-
end
-
-
1
def self.reset_cache
-
@@all_cached_fixtures.clear
-
end
-
-
1
def self.cache_for_connection(connection)
-
1
@@all_cached_fixtures[connection]
-
end
-
-
1
def self.fixture_is_cached?(connection, table_name)
-
cache_for_connection(connection)[table_name]
-
end
-
-
1
def self.cached_fixtures(connection, keys_to_fetch = nil)
-
1
if keys_to_fetch
-
1
cache_for_connection(connection).values_at(*keys_to_fetch)
-
else
-
cache_for_connection(connection).values
-
end
-
end
-
-
1
def self.cache_fixtures(connection, fixtures_map)
-
cache_for_connection(connection).update(fixtures_map)
-
end
-
-
1
def self.instantiate_fixtures(object, fixture_set, load_instances = true)
-
if load_instances
-
fixture_set.each do |fixture_name, fixture|
-
begin
-
object.instance_variable_set "@#{fixture_name}", fixture.find
-
rescue FixtureClassNotFound
-
nil
-
end
-
end
-
end
-
end
-
-
1
def self.instantiate_all_loaded_fixtures(object, load_instances = true)
-
all_loaded_fixtures.each_value do |fixture_set|
-
instantiate_fixtures(object, fixture_set, load_instances)
-
end
-
end
-
-
1
cattr_accessor :all_loaded_fixtures
-
1
self.all_loaded_fixtures = {}
-
-
1
class ClassCache
-
1
def initialize(class_names, config)
-
1
@class_names = class_names.stringify_keys
-
1
@config = config
-
-
# Remove string values that aren't constants or subclasses of AR
-
1
@class_names.delete_if { |klass_name, klass| !insert_class(@class_names, klass_name, klass) }
-
end
-
-
1
def [](fs_name)
-
@class_names.fetch(fs_name) {
-
klass = default_fixture_model(fs_name, @config).safe_constantize
-
insert_class(@class_names, fs_name, klass)
-
}
-
end
-
-
1
private
-
-
1
def insert_class(class_names, name, klass)
-
# We only want to deal with AR objects.
-
if klass && klass < ActiveRecord::Base
-
class_names[name] = klass
-
else
-
class_names[name] = nil
-
end
-
end
-
-
1
def default_fixture_model(fs_name, config)
-
ActiveRecord::FixtureSet.default_fixture_model_name(fs_name, config)
-
end
-
end
-
-
1
def self.create_fixtures(fixtures_directory, fixture_set_names, class_names = {}, config = ActiveRecord::Base)
-
1
fixture_set_names = Array(fixture_set_names).map(&:to_s)
-
1
class_names = ClassCache.new class_names, config
-
-
# FIXME: Apparently JK uses this.
-
1
connection = block_given? ? yield : ActiveRecord::Base.connection
-
-
1
files_to_read = fixture_set_names.reject { |fs_name|
-
fixture_is_cached?(connection, fs_name)
-
}
-
-
1
unless files_to_read.empty?
-
connection.disable_referential_integrity do
-
fixtures_map = {}
-
-
fixture_sets = files_to_read.map do |fs_name|
-
klass = class_names[fs_name]
-
conn = klass ? klass.connection : connection
-
fixtures_map[fs_name] = new( # ActiveRecord::FixtureSet.new
-
conn,
-
fs_name,
-
klass,
-
::File.join(fixtures_directory, fs_name))
-
end
-
-
update_all_loaded_fixtures fixtures_map
-
-
connection.transaction(:requires_new => true) do
-
fixture_sets.each do |fs|
-
conn = fs.model_class.respond_to?(:connection) ? fs.model_class.connection : connection
-
table_rows = fs.table_rows
-
-
table_rows.each_key do |table|
-
conn.delete "DELETE FROM #{conn.quote_table_name(table)}", 'Fixture Delete'
-
end
-
-
table_rows.each do |fixture_set_name, rows|
-
rows.each do |row|
-
conn.insert_fixture(row, fixture_set_name)
-
end
-
end
-
-
# Cap primary key sequences to max(pk).
-
if conn.respond_to?(:reset_pk_sequence!)
-
conn.reset_pk_sequence!(fs.table_name)
-
end
-
end
-
end
-
-
cache_fixtures(connection, fixtures_map)
-
end
-
end
-
1
cached_fixtures(connection, fixture_set_names)
-
end
-
-
# Returns a consistent, platform-independent identifier for +label+.
-
# Integer identifiers are values less than 2^30. UUIDs are RFC 4122 version 5 SHA-1 hashes.
-
1
def self.identify(label, column_type = :integer)
-
if column_type == :uuid
-
Digest::UUID.uuid_v5(Digest::UUID::OID_NAMESPACE, label.to_s)
-
else
-
Zlib.crc32(label.to_s) % MAX_ID
-
end
-
end
-
-
# Superclass for the evaluation contexts used by ERB fixtures.
-
1
def self.context_class
-
@context_class ||= Class.new
-
end
-
-
1
def self.update_all_loaded_fixtures(fixtures_map) # :nodoc:
-
all_loaded_fixtures.update(fixtures_map)
-
end
-
-
1
attr_reader :table_name, :name, :fixtures, :model_class, :config
-
-
1
def initialize(connection, name, class_name, path, config = ActiveRecord::Base)
-
@name = name
-
@path = path
-
@config = config
-
@model_class = nil
-
-
if class_name.is_a?(Class) # TODO: Should be an AR::Base type class, or any?
-
@model_class = class_name
-
else
-
@model_class = class_name.safe_constantize if class_name
-
end
-
-
@connection = connection
-
-
@table_name = ( model_class.respond_to?(:table_name) ?
-
model_class.table_name :
-
self.class.default_fixture_table_name(name, config) )
-
-
@fixtures = read_fixture_files path, @model_class
-
end
-
-
1
def [](x)
-
fixtures[x]
-
end
-
-
1
def []=(k,v)
-
fixtures[k] = v
-
end
-
-
1
def each(&block)
-
fixtures.each(&block)
-
end
-
-
1
def size
-
fixtures.size
-
end
-
-
# Returns a hash of rows to be inserted. The key is the table, the value is
-
# a list of rows to insert to that table.
-
1
def table_rows
-
now = config.default_timezone == :utc ? Time.now.utc : Time.now
-
now = now.to_s(:db)
-
-
# allow a standard key to be used for doing defaults in YAML
-
fixtures.delete('DEFAULTS')
-
-
# track any join tables we need to insert later
-
rows = Hash.new { |h,table| h[table] = [] }
-
-
rows[table_name] = fixtures.map do |label, fixture|
-
row = fixture.to_hash
-
-
if model_class
-
# fill in timestamp columns if they aren't specified and the model is set to record_timestamps
-
if model_class.record_timestamps
-
timestamp_column_names.each do |c_name|
-
row[c_name] = now unless row.key?(c_name)
-
end
-
end
-
-
# interpolate the fixture label
-
row.each do |key, value|
-
row[key] = value.gsub("$LABEL", label.to_s) if value.is_a?(String)
-
end
-
-
# generate a primary key if necessary
-
if has_primary_key_column? && !row.include?(primary_key_name)
-
row[primary_key_name] = ActiveRecord::FixtureSet.identify(label, primary_key_type)
-
end
-
-
# If STI is used, find the correct subclass for association reflection
-
reflection_class =
-
if row.include?(inheritance_column_name)
-
row[inheritance_column_name].constantize rescue model_class
-
else
-
model_class
-
end
-
-
reflection_class._reflections.each_value do |association|
-
case association.macro
-
when :belongs_to
-
# Do not replace association name with association foreign key if they are named the same
-
fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
-
-
if association.name.to_s != fk_name && value = row.delete(association.name.to_s)
-
if association.polymorphic? && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
-
# support polymorphic belongs_to as "label (Type)"
-
row[association.foreign_type] = $1
-
end
-
-
fk_type = reflection_class.columns_hash[fk_name].type
-
row[fk_name] = ActiveRecord::FixtureSet.identify(value, fk_type)
-
end
-
when :has_many
-
if association.options[:through]
-
add_join_records(rows, row, HasManyThroughProxy.new(association))
-
end
-
end
-
end
-
end
-
-
row
-
end
-
rows
-
end
-
-
1
class ReflectionProxy # :nodoc:
-
1
def initialize(association)
-
@association = association
-
end
-
-
1
def join_table
-
@association.join_table
-
end
-
-
1
def name
-
@association.name
-
end
-
-
1
def primary_key_type
-
@association.klass.column_types[@association.klass.primary_key].type
-
end
-
end
-
-
1
class HasManyThroughProxy < ReflectionProxy # :nodoc:
-
1
def rhs_key
-
@association.foreign_key
-
end
-
-
1
def lhs_key
-
@association.through_reflection.foreign_key
-
end
-
-
1
def join_table
-
@association.through_reflection.table_name
-
end
-
end
-
-
1
private
-
1
def primary_key_name
-
@primary_key_name ||= model_class && model_class.primary_key
-
end
-
-
1
def primary_key_type
-
@primary_key_type ||= model_class && model_class.column_types[model_class.primary_key].type
-
end
-
-
1
def add_join_records(rows, row, association)
-
# This is the case when the join table has no fixtures file
-
if (targets = row.delete(association.name.to_s))
-
table_name = association.join_table
-
column_type = association.primary_key_type
-
lhs_key = association.lhs_key
-
rhs_key = association.rhs_key
-
-
targets = targets.is_a?(Array) ? targets : targets.split(/\s*,\s*/)
-
rows[table_name].concat targets.map { |target|
-
{ lhs_key => row[primary_key_name],
-
rhs_key => ActiveRecord::FixtureSet.identify(target, column_type) }
-
}
-
end
-
end
-
-
1
def has_primary_key_column?
-
@has_primary_key_column ||= primary_key_name &&
-
model_class.columns.any? { |c| c.name == primary_key_name }
-
end
-
-
1
def timestamp_column_names
-
@timestamp_column_names ||=
-
%w(created_at created_on updated_at updated_on) & column_names
-
end
-
-
1
def inheritance_column_name
-
@inheritance_column_name ||= model_class && model_class.inheritance_column
-
end
-
-
1
def column_names
-
@column_names ||= @connection.columns(@table_name).collect { |c| c.name }
-
end
-
-
1
def read_fixture_files(path, model_class)
-
yaml_files = Dir["#{path}/{**,*}/*.yml"].select { |f|
-
::File.file?(f)
-
} + [yaml_file_path(path)]
-
-
yaml_files.each_with_object({}) do |file, fixtures|
-
FixtureSet::File.open(file) do |fh|
-
fh.each do |fixture_name, row|
-
fixtures[fixture_name] = ActiveRecord::Fixture.new(row, model_class)
-
end
-
end
-
end
-
end
-
-
1
def yaml_file_path(path)
-
"#{path}.yml"
-
end
-
-
end
-
-
#--
-
# Deprecate 'Fixtures' in favor of 'FixtureSet'.
-
#++
-
# :nodoc:
-
1
Fixtures = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('ActiveRecord::Fixtures', 'ActiveRecord::FixtureSet')
-
-
1
class Fixture #:nodoc:
-
1
include Enumerable
-
-
1
class FixtureError < StandardError #:nodoc:
-
end
-
-
1
class FormatError < FixtureError #:nodoc:
-
end
-
-
1
attr_reader :model_class, :fixture
-
-
1
def initialize(fixture, model_class)
-
@fixture = fixture
-
@model_class = model_class
-
end
-
-
1
def class_name
-
model_class.name if model_class
-
end
-
-
1
def each
-
fixture.each { |item| yield item }
-
end
-
-
1
def [](key)
-
fixture[key]
-
end
-
-
1
alias :to_hash :fixture
-
-
1
def find
-
if model_class
-
model_class.unscoped do
-
model_class.find(fixture[model_class.primary_key])
-
end
-
else
-
raise FixtureClassNotFound, "No class attached to find."
-
end
-
end
-
end
-
end
-
-
1
module ActiveRecord
-
1
module TestFixtures
-
1
extend ActiveSupport::Concern
-
-
1
def before_setup
-
7
setup_fixtures
-
7
super
-
end
-
-
1
def after_teardown
-
7
super
-
7
teardown_fixtures
-
end
-
-
1
included do
-
2
class_attribute :fixture_path, :instance_writer => false
-
2
class_attribute :fixture_table_names
-
2
class_attribute :fixture_class_names
-
2
class_attribute :use_transactional_fixtures
-
2
class_attribute :use_instantiated_fixtures # true, false, or :no_instances
-
2
class_attribute :pre_loaded_fixtures
-
2
class_attribute :config
-
-
2
self.fixture_table_names = []
-
2
self.use_transactional_fixtures = true
-
2
self.use_instantiated_fixtures = false
-
2
self.pre_loaded_fixtures = false
-
2
self.config = ActiveRecord::Base
-
-
2
self.fixture_class_names = Hash.new do |h, fixture_set_name|
-
h[fixture_set_name] = ActiveRecord::FixtureSet.default_fixture_model_name(fixture_set_name, self.config)
-
end
-
end
-
-
1
module ClassMethods
-
# Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
-
#
-
# Examples:
-
#
-
# set_fixture_class some_fixture: SomeModel,
-
# 'namespaced/fixture' => Another::Model
-
#
-
# The keys must be the fixture names, that coincide with the short paths to the fixture files.
-
1
def set_fixture_class(class_names = {})
-
self.fixture_class_names = self.fixture_class_names.merge(class_names.stringify_keys)
-
end
-
-
1
def fixtures(*fixture_set_names)
-
if fixture_set_names.first == :all
-
fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"]
-
fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
-
else
-
fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
-
end
-
-
self.fixture_table_names |= fixture_set_names
-
setup_fixture_accessors(fixture_set_names)
-
end
-
-
1
def setup_fixture_accessors(fixture_set_names = nil)
-
fixture_set_names = Array(fixture_set_names || fixture_table_names)
-
methods = Module.new do
-
fixture_set_names.each do |fs_name|
-
fs_name = fs_name.to_s
-
accessor_name = fs_name.tr('/', '_').to_sym
-
-
define_method(accessor_name) do |*fixture_names|
-
force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
-
-
@fixture_cache[fs_name] ||= {}
-
-
instances = fixture_names.map do |f_name|
-
f_name = f_name.to_s
-
@fixture_cache[fs_name].delete(f_name) if force_reload
-
-
if @loaded_fixtures[fs_name][f_name]
-
@fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
-
else
-
raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
-
end
-
end
-
-
instances.size == 1 ? instances.first : instances
-
end
-
private accessor_name
-
end
-
end
-
include methods
-
end
-
-
1
def uses_transaction(*methods)
-
@uses_transaction = [] unless defined?(@uses_transaction)
-
@uses_transaction.concat methods.map { |m| m.to_s }
-
end
-
-
1
def uses_transaction?(method)
-
14
@uses_transaction = [] unless defined?(@uses_transaction)
-
14
@uses_transaction.include?(method.to_s)
-
end
-
end
-
-
1
def run_in_transaction?
-
use_transactional_fixtures &&
-
14
!self.class.uses_transaction?(method_name)
-
end
-
-
1
def setup_fixtures(config = ActiveRecord::Base)
-
7
if pre_loaded_fixtures && !use_transactional_fixtures
-
raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
-
end
-
-
7
@fixture_cache = {}
-
7
@fixture_connections = []
-
7
@@already_loaded_fixtures ||= {}
-
-
# Load fixtures once and begin transaction.
-
7
if run_in_transaction?
-
7
if @@already_loaded_fixtures[self.class]
-
6
@loaded_fixtures = @@already_loaded_fixtures[self.class]
-
else
-
1
@loaded_fixtures = load_fixtures(config)
-
1
@@already_loaded_fixtures[self.class] = @loaded_fixtures
-
end
-
7
@fixture_connections = enlist_fixture_connections
-
7
@fixture_connections.each do |connection|
-
7
connection.begin_transaction joinable: false
-
end
-
# Load fixtures for every test.
-
else
-
ActiveRecord::FixtureSet.reset_cache
-
@@already_loaded_fixtures[self.class] = nil
-
@loaded_fixtures = load_fixtures(config)
-
end
-
-
# Instantiate fixtures for every test if requested.
-
7
instantiate_fixtures if use_instantiated_fixtures
-
end
-
-
1
def teardown_fixtures
-
# Rollback changes if a transaction is active.
-
7
if run_in_transaction?
-
7
@fixture_connections.each do |connection|
-
7
connection.rollback_transaction if connection.transaction_open?
-
end
-
7
@fixture_connections.clear
-
else
-
ActiveRecord::FixtureSet.reset_cache
-
end
-
-
7
ActiveRecord::Base.clear_active_connections!
-
end
-
-
1
def enlist_fixture_connections
-
7
ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
-
end
-
-
1
private
-
1
def load_fixtures(config)
-
1
fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config)
-
1
Hash[fixtures.map { |f| [f.name, f] }]
-
end
-
-
1
def instantiate_fixtures
-
if pre_loaded_fixtures
-
raise RuntimeError, 'Load fixtures before instantiating them.' if ActiveRecord::FixtureSet.all_loaded_fixtures.empty?
-
ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?)
-
else
-
raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
-
@loaded_fixtures.each_value do |fixture_set|
-
ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?)
-
end
-
end
-
end
-
-
1
def load_instances?
-
use_instantiated_fixtures != :no_instances
-
end
-
end
-
end
-
-
1
class ActiveRecord::FixtureSet::RenderContext # :nodoc:
-
1
def self.create_subclass
-
Class.new ActiveRecord::FixtureSet.context_class do
-
def get_binding
-
binding()
-
end
-
end
-
end
-
end
-
# -*- coding: utf-8 -*-
-
-
1
module ActiveRecord
-
1
module NullRelation # :nodoc:
-
1
def exec_queries
-
@records = []
-
end
-
-
1
def pluck(*column_names)
-
[]
-
end
-
-
1
def delete_all(_conditions = nil)
-
0
-
end
-
-
1
def update_all(_updates, _conditions = nil, _options = {})
-
0
-
end
-
-
1
def delete(_id_or_array)
-
0
-
end
-
-
1
def size
-
calculate :size, nil
-
end
-
-
1
def empty?
-
true
-
end
-
-
1
def any?
-
false
-
end
-
-
1
def many?
-
false
-
end
-
-
1
def to_sql
-
""
-
end
-
-
1
def count(*)
-
calculate :count, nil
-
end
-
-
1
def sum(*)
-
calculate :sum, nil
-
end
-
-
1
def average(*)
-
calculate :average, nil
-
end
-
-
1
def minimum(*)
-
calculate :minimum, nil
-
end
-
-
1
def maximum(*)
-
calculate :maximum, nil
-
end
-
-
1
def calculate(operation, _column_name, _options = {})
-
# TODO: Remove _options argument as soon we remove support to
-
# activerecord-deprecated_finders.
-
if [:count, :sum, :size].include? operation
-
group_values.any? ? Hash.new : 0
-
elsif [:average, :minimum, :maximum].include?(operation) && group_values.any?
-
Hash.new
-
else
-
nil
-
end
-
end
-
-
1
def exists?(_id = false)
-
false
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
class PredicateBuilder # :nodoc:
-
1
@handlers = []
-
-
1
autoload :RelationHandler, 'active_record/relation/predicate_builder/relation_handler'
-
1
autoload :ArrayHandler, 'active_record/relation/predicate_builder/array_handler'
-
-
1
def self.resolve_column_aliases(klass, hash)
-
# This method is a hot spot, so for now, use Hash[] to dup the hash.
-
# https://bugs.ruby-lang.org/issues/7166
-
28
hash = Hash[hash]
-
28
hash.keys.grep(Symbol) do |key|
-
26
if klass.attribute_alias? key
-
hash[klass.attribute_alias(key)] = hash.delete key
-
end
-
end
-
28
hash
-
end
-
-
1
def self.build_from_hash(klass, attributes, default_table)
-
28
queries = []
-
-
28
attributes.each do |column, value|
-
46
table = default_table
-
-
46
if value.is_a?(Hash)
-
if value.empty?
-
queries << '1=0'
-
else
-
table = Arel::Table.new(column, default_table.engine)
-
association = klass._reflect_on_association(column)
-
-
value.each do |k, v|
-
queries.concat expand(association && association.klass, table, k, v)
-
end
-
end
-
else
-
46
column = column.to_s
-
-
46
if column.include?('.')
-
table_name, column = column.split('.', 2)
-
table = Arel::Table.new(table_name, default_table.engine)
-
end
-
-
46
queries.concat expand(klass, table, column, value)
-
end
-
end
-
-
28
queries
-
end
-
-
1
def self.expand(klass, table, column, value)
-
46
queries = []
-
-
# Find the foreign key when using queries such as:
-
# Post.where(author: author)
-
#
-
# For polymorphic relationships, find the foreign key and type:
-
# PriceEstimate.where(estimate_of: treasure)
-
46
if klass && reflection = klass._reflect_on_association(column)
-
18
base_class = polymorphic_base_class_from_value(value)
-
-
18
if reflection.polymorphic? && base_class
-
queries << build(table[reflection.foreign_type], base_class)
-
end
-
-
18
column = reflection.foreign_key
-
-
18
if base_class
-
18
primary_key = reflection.association_primary_key(base_class)
-
18
value = convert_value_to_association_ids(value, primary_key)
-
end
-
end
-
-
46
queries << build(table[column], value)
-
46
queries
-
end
-
-
1
def self.polymorphic_base_class_from_value(value)
-
18
case value
-
when Relation
-
value.klass.base_class
-
when Array
-
val = value.compact.first
-
val.class.base_class if val.is_a?(Base)
-
when Base
-
18
value.class.base_class
-
end
-
end
-
-
1
def self.references(attributes)
-
attributes.map do |key, value|
-
46
if value.is_a?(Hash)
-
key
-
else
-
46
key = key.to_s
-
46
key.split('.').first if key.include?('.')
-
end
-
28
end.compact
-
end
-
-
# Define how a class is converted to Arel nodes when passed to +where+.
-
# The handler can be any object that responds to +call+, and will be used
-
# for any value that +===+ the class given. For example:
-
#
-
# MyCustomDateRange = Struct.new(:start, :end)
-
# handler = proc do |column, range|
-
# Arel::Nodes::Between.new(column,
-
# Arel::Nodes::And.new([range.start, range.end])
-
# )
-
# end
-
# ActiveRecord::PredicateBuilder.register_handler(MyCustomDateRange, handler)
-
1
def self.register_handler(klass, handler)
-
6
@handlers.unshift([klass, handler])
-
end
-
-
47
BASIC_OBJECT_HANDLER = ->(attribute, value) { attribute.eq(value) } # :nodoc:
-
1
register_handler(BasicObject, BASIC_OBJECT_HANDLER)
-
# FIXME: I think we need to deprecate this behavior
-
1
register_handler(Class, ->(attribute, value) { attribute.eq(value.name) })
-
1
register_handler(Base, ->(attribute, value) { attribute.eq(value.id) })
-
1
register_handler(Range, ->(attribute, value) { attribute.between(value) })
-
1
register_handler(Relation, RelationHandler.new)
-
1
register_handler(Array, ArrayHandler.new)
-
-
1
def self.build(attribute, value)
-
46
handler_for(value).call(attribute, value)
-
end
-
1
private_class_method :build
-
-
1
def self.handler_for(object)
-
608
@handlers.detect { |klass, _| klass === object }.last
-
end
-
1
private_class_method :handler_for
-
-
1
def self.convert_value_to_association_ids(value, primary_key)
-
18
case value
-
when Relation
-
value.select(primary_key)
-
when Array
-
value.map { |v| convert_value_to_association_ids(v, primary_key) }
-
when Base
-
18
value._read_attribute(primary_key)
-
else
-
value
-
end
-
end
-
-
1
def self.can_be_bound?(value) # :nodoc:
-
!value.nil? &&
-
46
!value.is_a?(Hash) &&
-
handler_for(value) == BASIC_OBJECT_HANDLER
-
end
-
end
-
end
-
1
require 'active_support/core_ext/string/filters'
-
-
1
module ActiveRecord
-
1
class PredicateBuilder
-
1
class ArrayHandler # :nodoc:
-
1
def call(attribute, value)
-
values = value.map { |x| x.is_a?(Base) ? x.id : x }
-
nils, values = values.partition(&:nil?)
-
-
if values.any? { |val| val.is_a?(Array) }
-
ActiveSupport::Deprecation.warn(<<-MSG.squish)
-
Passing a nested array to Active Record finder methods is
-
deprecated and will be removed. Flatten your array before using
-
it for 'IN' conditions.
-
MSG
-
-
values = values.flatten
-
end
-
-
return attribute.in([]) if values.empty? && nils.empty?
-
-
ranges, values = values.partition { |v| v.is_a?(Range) }
-
-
values_predicate =
-
case values.length
-
when 0 then NullPredicate
-
when 1 then attribute.eq(values.first)
-
else attribute.in(values)
-
end
-
-
unless nils.empty?
-
values_predicate = values_predicate.or(attribute.eq(nil))
-
end
-
-
array_predicates = ranges.map { |range| attribute.between(range) }
-
array_predicates.unshift(values_predicate)
-
array_predicates.inject { |composite, predicate| composite.or(predicate) }
-
end
-
-
1
module NullPredicate # :nodoc:
-
1
def self.or(other)
-
other
-
end
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
1
class PredicateBuilder
-
1
class RelationHandler # :nodoc:
-
1
def call(attribute, value)
-
if value.select_values.empty?
-
value = value.select(value.klass.arel_table[value.klass.primary_key])
-
end
-
-
attribute.in(value.arel)
-
end
-
end
-
end
-
end
-
1
module ActiveRecord
-
###
-
# This class encapsulates a Result returned from calling +exec_query+ on any
-
# database connection adapter. For example:
-
#
-
# result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
-
# result # => #<ActiveRecord::Result:0xdeadbeef>
-
#
-
# # Get the column names of the result:
-
# result.columns
-
# # => ["id", "title", "body"]
-
#
-
# # Get the record values of the result:
-
# result.rows
-
# # => [[1, "title_1", "body_1"],
-
# [2, "title_2", "body_2"],
-
# ...
-
# ]
-
#
-
# # Get an array of hashes representing the result (column => value):
-
# result.to_hash
-
# # => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
-
# {"id" => 2, "title" => "title_2", "body" => "body_2"},
-
# ...
-
# ]
-
#
-
# # ActiveRecord::Result also includes Enumerable.
-
# result.each do |row|
-
# puts row['title'] + " " + row['body']
-
# end
-
1
class Result
-
1
include Enumerable
-
-
1
IDENTITY_TYPE = Type::Value.new # :nodoc:
-
-
1
attr_reader :columns, :rows, :column_types
-
-
1
def initialize(columns, rows, column_types = {})
-
312
@columns = columns
-
312
@rows = rows
-
312
@hash_rows = nil
-
312
@column_types = column_types
-
end
-
-
1
def length
-
231
@rows.length
-
end
-
-
1
def each
-
260
if block_given?
-
551
hash_rows.each { |row| yield row }
-
else
-
hash_rows.to_enum { @rows.size }
-
end
-
end
-
-
1
def to_hash
-
hash_rows
-
end
-
-
1
alias :map! :map
-
1
alias :collect! :map
-
-
# Returns true if there are no records.
-
1
def empty?
-
rows.empty?
-
end
-
-
1
def to_ary
-
hash_rows
-
end
-
-
1
def [](idx)
-
hash_rows[idx]
-
end
-
-
1
def last
-
hash_rows.last
-
end
-
-
1
def cast_values(type_overrides = {}) # :nodoc:
-
types = columns.map { |name| column_type(name, type_overrides) }
-
result = rows.map do |values|
-
types.zip(values).map { |type, value| type.type_cast_from_database(value) }
-
end
-
-
columns.one? ? result.map!(&:first) : result
-
end
-
-
1
def initialize_copy(other)
-
@columns = columns.dup
-
@rows = rows.dup
-
@column_types = column_types.dup
-
@hash_rows = nil
-
end
-
-
1
private
-
-
1
def column_type(name, type_overrides = {})
-
type_overrides.fetch(name) do
-
column_types.fetch(name, IDENTITY_TYPE)
-
end
-
end
-
-
1
def hash_rows
-
@hash_rows ||=
-
begin
-
# We freeze the strings to prevent them getting duped when
-
# used as keys in ActiveRecord::Base's @attributes hash
-
4192
columns = @columns.map { |c| c.dup.freeze }
-
260
@rows.map { |row|
-
# In the past we used Hash[columns.zip(row)]
-
# though elegant, the verbose way is much more efficient
-
# both time and memory wise cause it avoids a big array allocation
-
# this method is called a lot and needs to be micro optimised
-
291
hash = {}
-
-
291
index = 0
-
291
length = columns.length
-
-
291
while index < length
-
4209
hash[columns[index]] = row[index]
-
4209
index += 1
-
end
-
-
291
hash
-
}
-
260
end
-
end
-
end
-
end
-
1
require 'active_record/scoping/default'
-
1
require 'active_record/scoping/named'
-
1
require 'active_record/base'
-
-
1
module ActiveRecord
-
1
class SchemaMigration < ActiveRecord::Base
-
1
class << self
-
1
def primary_key
-
nil
-
end
-
-
1
def table_name
-
4
"#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
-
end
-
-
1
def index_name
-
"#{table_name_prefix}unique_#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
-
end
-
-
1
def table_exists?
-
connection.table_exists?(table_name)
-
end
-
-
1
def create_table(limit=nil)
-
unless table_exists?
-
version_options = {null: false}
-
version_options[:limit] = limit if limit
-
-
connection.create_table(table_name, id: false) do |t|
-
t.column :version, :string, version_options
-
end
-
connection.add_index table_name, :version, unique: true, name: index_name
-
end
-
end
-
-
1
def drop_table
-
if table_exists?
-
connection.remove_index table_name, name: index_name
-
connection.drop_table(table_name)
-
end
-
end
-
-
1
def normalize_migration_number(number)
-
"%.3d" % number.to_i
-
end
-
-
1
def normalized_versions
-
pluck(:version).map { |v| normalize_migration_number v }
-
end
-
end
-
-
1
def version
-
13
super.to_i
-
end
-
end
-
end
-
1
module ActiveRecord
-
-
# Statement cache is used to cache a single statement in order to avoid creating the AST again.
-
# Initializing the cache is done by passing the statement in the create block:
-
#
-
# cache = StatementCache.create(Book.connection) do |params|
-
# Book.where(name: "my book").where("author_id > 3")
-
# end
-
#
-
# The cached statement is executed by using the +execute+ method:
-
#
-
# cache.execute([], Book, Book.connection)
-
#
-
# The relation returned by the block is cached, and for each +execute+ call the cached relation gets duped.
-
# Database is queried when +to_a+ is called on the relation.
-
#
-
# If you want to cache the statement without the values you can use the +bind+ method of the
-
# block parameter.
-
#
-
# cache = StatementCache.create(Book.connection) do |params|
-
# Book.where(name: params.bind)
-
# end
-
#
-
# And pass the bind values as the first argument of +execute+ call.
-
#
-
# cache.execute(["my book"], Book, Book.connection)
-
1
class StatementCache # :nodoc:
-
1
class Substitute; end # :nodoc:
-
-
1
class Query # :nodoc:
-
1
def initialize(sql)
-
6
@sql = sql
-
end
-
-
1
def sql_for(binds, connection)
-
204
@sql
-
end
-
end
-
-
1
class PartialQuery < Query # :nodoc:
-
1
def initialize values
-
@values = values
-
@indexes = values.each_with_index.find_all { |thing,i|
-
Arel::Nodes::BindParam === thing
-
}.map(&:last)
-
end
-
-
1
def sql_for(binds, connection)
-
val = @values.dup
-
binds = binds.dup
-
@indexes.each { |i| val[i] = connection.quote(*binds.shift.reverse) }
-
val.join
-
end
-
end
-
-
1
def self.query(visitor, ast)
-
6
Query.new visitor.accept(ast, Arel::Collectors::SQLString.new).value
-
end
-
-
1
def self.partial_query(visitor, ast, collector)
-
collected = visitor.accept(ast, collector).value
-
PartialQuery.new collected
-
end
-
-
1
class Params # :nodoc:
-
7
def bind; Substitute.new; end
-
end
-
-
1
class BindMap # :nodoc:
-
1
def initialize(bind_values)
-
6
@indexes = []
-
6
@bind_values = bind_values
-
-
6
bind_values.each_with_index do |(_, value), i|
-
6
if Substitute === value
-
6
@indexes << i
-
end
-
end
-
end
-
-
1
def bind(values)
-
408
bvs = @bind_values.map { |pair| pair.dup }
-
408
@indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] }
-
204
bvs
-
end
-
end
-
-
1
attr_reader :bind_map, :query_builder
-
-
1
def self.create(connection, block = Proc.new)
-
6
relation = block.call Params.new
-
6
bind_map = BindMap.new relation.bind_values
-
6
query_builder = connection.cacheable_query relation.arel
-
6
new query_builder, bind_map
-
end
-
-
1
def initialize(query_builder, bind_map)
-
6
@query_builder = query_builder
-
6
@bind_map = bind_map
-
end
-
-
1
def execute(params, klass, connection)
-
204
bind_values = bind_map.bind params
-
-
204
sql = query_builder.sql_for bind_values, connection
-
-
204
klass.find_by_sql sql, bind_values
-
end
-
1
alias :call :execute
-
end
-
end
-
1
require 'securerandom'
-
-
1
module Digest
-
1
module UUID
-
1
DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
-
1
URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
-
1
OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
-
1
X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc:
-
-
# Generates a v5 non-random UUID (Universally Unique IDentifier).
-
#
-
# Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs.
-
# uuid_from_hash always generates the same UUID for a given name and namespace combination.
-
#
-
# See RFC 4122 for details of UUID at: http://www.ietf.org/rfc/rfc4122.txt
-
1
def self.uuid_from_hash(hash_class, uuid_namespace, name)
-
if hash_class == Digest::MD5
-
version = 3
-
elsif hash_class == Digest::SHA1
-
version = 5
-
else
-
raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}."
-
end
-
-
hash = hash_class.new
-
hash.update(uuid_namespace)
-
hash.update(name)
-
-
ary = hash.digest.unpack('NnnnnN')
-
ary[2] = (ary[2] & 0x0FFF) | (version << 12)
-
ary[3] = (ary[3] & 0x3FFF) | 0x8000
-
-
"%08x-%04x-%04x-%04x-%04x%08x" % ary
-
end
-
-
# Convenience method for uuid_from_hash using Digest::MD5.
-
1
def self.uuid_v3(uuid_namespace, name)
-
self.uuid_from_hash(Digest::MD5, uuid_namespace, name)
-
end
-
-
# Convenience method for uuid_from_hash using Digest::SHA1.
-
1
def self.uuid_v5(uuid_namespace, name)
-
self.uuid_from_hash(Digest::SHA1, uuid_namespace, name)
-
end
-
-
# Convenience method for SecureRandom.uuid.
-
1
def self.uuid_v4
-
SecureRandom.uuid
-
end
-
end
-
end
-
# encoding: utf-8
-
1
require 'active_support/json'
-
1
require 'active_support/core_ext/string/access'
-
1
require 'active_support/core_ext/string/behavior'
-
1
require 'active_support/core_ext/module/delegation'
-
-
1
module ActiveSupport #:nodoc:
-
1
module Multibyte #:nodoc:
-
# Chars enables you to work transparently with UTF-8 encoding in the Ruby
-
# String class without having extensive knowledge about the encoding. A
-
# Chars object accepts a string upon initialization and proxies String
-
# methods in an encoding safe manner. All the normal String methods are also
-
# implemented on the proxy.
-
#
-
# String methods are proxied through the Chars object, and can be accessed
-
# through the +mb_chars+ method. Methods which would normally return a
-
# String object now return a Chars object so methods can be chained.
-
#
-
# 'The Perfect String '.mb_chars.downcase.strip.normalize # => "the perfect string"
-
#
-
# Chars objects are perfectly interchangeable with String objects as long as
-
# no explicit class checks are made. If certain methods do explicitly check
-
# the class, call +to_s+ before you pass chars objects to them.
-
#
-
# bad.explicit_checking_method 'T'.mb_chars.downcase.to_s
-
#
-
# The default Chars implementation assumes that the encoding of the string
-
# is UTF-8, if you want to handle different encodings you can write your own
-
# multibyte string handler and configure it through
-
# ActiveSupport::Multibyte.proxy_class.
-
#
-
# class CharsForUTF32
-
# def size
-
# @wrapped_string.size / 4
-
# end
-
#
-
# def self.accepts?(string)
-
# string.length % 4 == 0
-
# end
-
# end
-
#
-
# ActiveSupport::Multibyte.proxy_class = CharsForUTF32
-
1
class Chars
-
1
include Comparable
-
1
attr_reader :wrapped_string
-
1
alias to_s wrapped_string
-
1
alias to_str wrapped_string
-
-
1
delegate :<=>, :=~, :acts_like_string?, :to => :wrapped_string
-
-
# Creates a new Chars instance by wrapping _string_.
-
1
def initialize(string)
-
@wrapped_string = string
-
@wrapped_string.force_encoding(Encoding::UTF_8) unless @wrapped_string.frozen?
-
end
-
-
# Forward all undefined methods to the wrapped string.
-
1
def method_missing(method, *args, &block)
-
result = @wrapped_string.__send__(method, *args, &block)
-
if method.to_s =~ /!$/
-
self if result
-
else
-
result.kind_of?(String) ? chars(result) : result
-
end
-
end
-
-
# Returns +true+ if _obj_ responds to the given method. Private methods
-
# are included in the search only if the optional second parameter
-
# evaluates to +true+.
-
1
def respond_to_missing?(method, include_private)
-
@wrapped_string.respond_to?(method, include_private)
-
end
-
-
# Returns +true+ when the proxy class can handle the string. Returns
-
# +false+ otherwise.
-
1
def self.consumes?(string)
-
string.encoding == Encoding::UTF_8
-
end
-
-
# Works just like <tt>String#split</tt>, with the exception that the items
-
# in the resulting list are Chars instances instead of String. This makes
-
# chaining methods easier.
-
#
-
# 'Café périferôl'.mb_chars.split(/é/).map { |part| part.upcase.to_s } # => ["CAF", " P", "RIFERÔL"]
-
1
def split(*args)
-
@wrapped_string.split(*args).map { |i| self.class.new(i) }
-
end
-
-
# Works like <tt>String#slice!</tt>, but returns an instance of
-
# Chars, or nil if the string was not modified.
-
1
def slice!(*args)
-
chars(@wrapped_string.slice!(*args))
-
end
-
-
# Reverses all characters in the string.
-
#
-
# 'Café'.mb_chars.reverse.to_s # => 'éfaC'
-
1
def reverse
-
chars(Unicode.unpack_graphemes(@wrapped_string).reverse.flatten.pack('U*'))
-
end
-
-
# Limits the byte size of the string to a number of bytes without breaking
-
# characters. Usable when the storage for a string is limited for some
-
# reason.
-
#
-
# 'こんにちは'.mb_chars.limit(7).to_s # => "こん"
-
1
def limit(limit)
-
slice(0...translate_offset(limit))
-
end
-
-
# Converts characters in the string to uppercase.
-
#
-
# 'Laurent, où sont les tests ?'.mb_chars.upcase.to_s # => "LAURENT, OÙ SONT LES TESTS ?"
-
1
def upcase
-
chars Unicode.upcase(@wrapped_string)
-
end
-
-
# Converts characters in the string to lowercase.
-
#
-
# 'VĚDA A VÝZKUM'.mb_chars.downcase.to_s # => "věda a výzkum"
-
1
def downcase
-
chars Unicode.downcase(@wrapped_string)
-
end
-
-
# Converts characters in the string to the opposite case.
-
#
-
# 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN"
-
1
def swapcase
-
chars Unicode.swapcase(@wrapped_string)
-
end
-
-
# Converts the first character to uppercase and the remainder to lowercase.
-
#
-
# 'über'.mb_chars.capitalize.to_s # => "Über"
-
1
def capitalize
-
(slice(0) || chars('')).upcase + (slice(1..-1) || chars('')).downcase
-
end
-
-
# Capitalizes the first letter of every word, when possible.
-
#
-
# "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
-
# "日本語".mb_chars.titleize # => "日本語"
-
1
def titleize
-
chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1)})
-
end
-
1
alias_method :titlecase, :titleize
-
-
# Returns the KC normalization of the string by default. NFKC is
-
# considered the best normalization form for passing strings to databases
-
# and validations.
-
#
-
# * <tt>form</tt> - The form you want to normalize in. Should be one of the following:
-
# <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. Default is
-
# ActiveSupport::Multibyte::Unicode.default_normalization_form
-
1
def normalize(form = nil)
-
chars(Unicode.normalize(@wrapped_string, form))
-
end
-
-
# Performs canonical decomposition on all the characters.
-
#
-
# 'é'.length # => 2
-
# 'é'.mb_chars.decompose.to_s.length # => 3
-
1
def decompose
-
chars(Unicode.decompose(:canonical, @wrapped_string.codepoints.to_a).pack('U*'))
-
end
-
-
# Performs composition on all the characters.
-
#
-
# 'é'.length # => 3
-
# 'é'.mb_chars.compose.to_s.length # => 2
-
1
def compose
-
chars(Unicode.compose(@wrapped_string.codepoints.to_a).pack('U*'))
-
end
-
-
# Returns the number of grapheme clusters in the string.
-
#
-
# 'क्षि'.mb_chars.length # => 4
-
# 'क्षि'.mb_chars.grapheme_length # => 3
-
1
def grapheme_length
-
Unicode.unpack_graphemes(@wrapped_string).length
-
end
-
-
# Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent
-
# resulting in a valid UTF-8 string.
-
#
-
# Passing +true+ will forcibly tidy all bytes, assuming that the string's
-
# encoding is entirely CP1252 or ISO-8859-1.
-
1
def tidy_bytes(force = false)
-
chars(Unicode.tidy_bytes(@wrapped_string, force))
-
end
-
-
1
def as_json(options = nil) #:nodoc:
-
to_s.as_json(options)
-
end
-
-
1
%w(capitalize downcase reverse tidy_bytes upcase).each do |method|
-
5
define_method("#{method}!") do |*args|
-
@wrapped_string = send(method, *args).to_s
-
self
-
end
-
end
-
-
1
protected
-
-
1
def translate_offset(byte_offset) #:nodoc:
-
return nil if byte_offset.nil?
-
return 0 if @wrapped_string == ''
-
-
begin
-
@wrapped_string.byteslice(0...byte_offset).unpack('U*').length
-
rescue ArgumentError
-
byte_offset -= 1
-
retry
-
end
-
end
-
-
1
def chars(string) #:nodoc:
-
self.class.new(string)
-
end
-
end
-
end
-
end
-
1
gem 'minitest' # make sure we get the gem, not stdlib
-
1
require 'minitest'
-
1
require 'active_support/testing/tagged_logging'
-
1
require 'active_support/testing/setup_and_teardown'
-
1
require 'active_support/testing/assertions'
-
1
require 'active_support/testing/deprecation'
-
1
require 'active_support/testing/declarative'
-
1
require 'active_support/testing/isolation'
-
1
require 'active_support/testing/constant_lookup'
-
1
require 'active_support/testing/time_helpers'
-
1
require 'active_support/core_ext/kernel/reporting'
-
1
require 'active_support/deprecation'
-
-
1
module ActiveSupport
-
1
class TestCase < ::Minitest::Test
-
1
Assertion = Minitest::Assertion
-
-
1
class << self
-
# Sets the order in which test cases are run.
-
#
-
# ActiveSupport::TestCase.test_order = :random # => :random
-
#
-
# Valid values are:
-
# * +:random+ (to run tests in random order)
-
# * +:parallel+ (to run tests in parallel)
-
# * +:sorted+ (to run tests alphabetically by method name)
-
# * +:alpha+ (equivalent to +:sorted+)
-
1
def test_order=(new_order)
-
ActiveSupport.test_order = new_order
-
end
-
-
# Returns the order in which test cases are run.
-
#
-
# ActiveSupport::TestCase.test_order # => :sorted
-
#
-
# Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+.
-
# Defaults to +:sorted+.
-
1
def test_order
-
test_order = ActiveSupport.test_order
-
-
if test_order.nil?
-
ActiveSupport::Deprecation.warn "You did not specify a value for the " \
-
"configuration option `active_support.test_order`. In Rails 5, " \
-
"the default value of this option will change from `:sorted` to " \
-
"`:random`.\n" \
-
"To disable this warning and keep the current behavior, you can add " \
-
"the following line to your `config/environments/test.rb`:\n" \
-
"\n" \
-
" Rails.application.configure do\n" \
-
" config.active_support.test_order = :sorted\n" \
-
" end\n" \
-
"\n" \
-
"Alternatively, you can opt into the future behavior by setting this " \
-
"option to `:random`."
-
-
test_order = :sorted
-
self.test_order = test_order
-
end
-
-
test_order
-
end
-
-
1
alias :my_tests_are_order_dependent! :i_suck_and_my_tests_are_order_dependent!
-
end
-
-
1
alias_method :method_name, :name
-
-
1
include ActiveSupport::Testing::TaggedLogging
-
1
include ActiveSupport::Testing::SetupAndTeardown
-
1
include ActiveSupport::Testing::Assertions
-
1
include ActiveSupport::Testing::Deprecation
-
1
include ActiveSupport::Testing::TimeHelpers
-
1
extend ActiveSupport::Testing::Declarative
-
-
# test/unit backwards compatibility methods
-
1
alias :assert_raise :assert_raises
-
1
alias :assert_not_empty :refute_empty
-
1
alias :assert_not_equal :refute_equal
-
1
alias :assert_not_in_delta :refute_in_delta
-
1
alias :assert_not_in_epsilon :refute_in_epsilon
-
1
alias :assert_not_includes :refute_includes
-
1
alias :assert_not_instance_of :refute_instance_of
-
1
alias :assert_not_kind_of :refute_kind_of
-
1
alias :assert_no_match :refute_match
-
1
alias :assert_not_nil :refute_nil
-
1
alias :assert_not_operator :refute_operator
-
1
alias :assert_not_predicate :refute_predicate
-
1
alias :assert_not_respond_to :refute_respond_to
-
1
alias :assert_not_same :refute_same
-
-
# Fails if the block raises an exception.
-
#
-
# assert_nothing_raised do
-
# ...
-
# end
-
1
def assert_nothing_raised(*args)
-
yield
-
end
-
end
-
end
-
1
require 'active_support/core_ext/object/blank'
-
-
1
module ActiveSupport
-
1
module Testing
-
1
module Assertions
-
# Assert that an expression is not truthy. Passes if <tt>object</tt> is
-
# +nil+ or +false+. "Truthy" means "considered true in a conditional"
-
# like <tt>if foo</tt>.
-
#
-
# assert_not nil # => true
-
# assert_not false # => true
-
# assert_not 'foo' # => Expected "foo" to be nil or false
-
#
-
# An error message can be specified.
-
#
-
# assert_not foo, 'foo should be false'
-
1
def assert_not(object, message = nil)
-
message ||= "Expected #{mu_pp(object)} to be nil or false"
-
assert !object, message
-
end
-
-
# Test numeric difference between the return value of an expression as a
-
# result of what is evaluated in the yielded block.
-
#
-
# assert_difference 'Article.count' do
-
# post :create, article: {...}
-
# end
-
#
-
# An arbitrary expression is passed in and evaluated.
-
#
-
# assert_difference 'assigns(:article).comments(:reload).size' do
-
# post :create, comment: {...}
-
# end
-
#
-
# An arbitrary positive or negative difference can be specified.
-
# The default is <tt>1</tt>.
-
#
-
# assert_difference 'Article.count', -1 do
-
# post :delete, id: ...
-
# end
-
#
-
# An array of expressions can also be passed in and evaluated.
-
#
-
# assert_difference [ 'Article.count', 'Post.count' ], 2 do
-
# post :create, article: {...}
-
# end
-
#
-
# A lambda or a list of lambdas can be passed in and evaluated:
-
#
-
# assert_difference ->{ Article.count }, 2 do
-
# post :create, article: {...}
-
# end
-
#
-
# assert_difference [->{ Article.count }, ->{ Post.count }], 2 do
-
# post :create, article: {...}
-
# end
-
#
-
# An error message can be specified.
-
#
-
# assert_difference 'Article.count', -1, 'An Article should be destroyed' do
-
# post :delete, id: ...
-
# end
-
1
def assert_difference(expression, difference = 1, message = nil, &block)
-
expressions = Array(expression)
-
-
exps = expressions.map { |e|
-
e.respond_to?(:call) ? e : lambda { eval(e, block.binding) }
-
}
-
before = exps.map { |e| e.call }
-
-
yield
-
-
expressions.zip(exps).each_with_index do |(code, e), i|
-
error = "#{code.inspect} didn't change by #{difference}"
-
error = "#{message}.\n#{error}" if message
-
assert_equal(before[i] + difference, e.call, error)
-
end
-
end
-
-
# Assertion that the numeric result of evaluating an expression is not
-
# changed before and after invoking the passed in block.
-
#
-
# assert_no_difference 'Article.count' do
-
# post :create, article: invalid_attributes
-
# end
-
#
-
# An error message can be specified.
-
#
-
# assert_no_difference 'Article.count', 'An Article should not be created' do
-
# post :create, article: invalid_attributes
-
# end
-
1
def assert_no_difference(expression, message = nil, &block)
-
assert_difference expression, 0, message, &block
-
end
-
end
-
end
-
end
-
1
require "active_support/concern"
-
1
require "active_support/inflector"
-
-
1
module ActiveSupport
-
1
module Testing
-
# Resolves a constant from a minitest spec name.
-
#
-
# Given the following spec-style test:
-
#
-
# describe WidgetsController, :index do
-
# describe "authenticated user" do
-
# describe "returns widgets" do
-
# it "has a controller that exists" do
-
# assert_kind_of WidgetsController, @controller
-
# end
-
# end
-
# end
-
# end
-
#
-
# The test will have the following name:
-
#
-
# "WidgetsController::index::authenticated user::returns widgets"
-
#
-
# The constant WidgetsController can be resolved from the name.
-
# The following code will resolve the constant:
-
#
-
# controller = determine_constant_from_test_name(name) do |constant|
-
# Class === constant && constant < ::ActionController::Metal
-
# end
-
1
module ConstantLookup
-
1
extend ::ActiveSupport::Concern
-
-
1
module ClassMethods # :nodoc:
-
1
def determine_constant_from_test_name(test_name)
-
names = test_name.split "::"
-
while names.size > 0 do
-
names.last.sub!(/Test$/, "")
-
begin
-
constant = names.join("::").safe_constantize
-
break(constant) if yield(constant)
-
ensure
-
names.pop
-
end
-
end
-
end
-
end
-
-
end
-
end
-
end
-
1
module ActiveSupport
-
1
module Testing
-
1
module Declarative
-
1
unless defined?(Spec)
-
# Helper to define a test method using a String. Under the hood, it replaces
-
# spaces with underscores and defines the test method.
-
#
-
# test "verify something" do
-
# ...
-
# end
-
1
def test(name, &block)
-
test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
-
defined = method_defined? test_name
-
raise "#{test_name} is already defined in #{self}" if defined
-
if block_given?
-
define_method(test_name, &block)
-
else
-
define_method(test_name) do
-
flunk "No implementation provided for #{name}"
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/deprecation'
-
-
1
module ActiveSupport
-
1
module Testing
-
1
module Deprecation #:nodoc:
-
1
def assert_deprecated(match = nil, &block)
-
result, warnings = collect_deprecations(&block)
-
assert !warnings.empty?, "Expected a deprecation warning within the block but received none"
-
if match
-
match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp)
-
assert warnings.any? { |w| w =~ match }, "No deprecation warning matched #{match}: #{warnings.join(', ')}"
-
end
-
result
-
end
-
-
1
def assert_not_deprecated(&block)
-
result, deprecations = collect_deprecations(&block)
-
assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}"
-
result
-
end
-
-
1
def collect_deprecations
-
old_behavior = ActiveSupport::Deprecation.behavior
-
deprecations = []
-
ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack|
-
deprecations << message
-
end
-
result = yield
-
[result, deprecations]
-
ensure
-
ActiveSupport::Deprecation.behavior = old_behavior
-
end
-
end
-
end
-
end
-
1
require 'active_support/concern'
-
1
require 'active_support/callbacks'
-
-
1
module ActiveSupport
-
1
module Testing
-
# Adds support for +setup+ and +teardown+ callbacks.
-
# These callbacks serve as a replacement to overwriting the
-
# <tt>#setup</tt> and <tt>#teardown</tt> methods of your TestCase.
-
#
-
# class ExampleTest < ActiveSupport::TestCase
-
# setup do
-
# # ...
-
# end
-
#
-
# teardown do
-
# # ...
-
# end
-
# end
-
1
module SetupAndTeardown
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
include ActiveSupport::Callbacks
-
1
define_callbacks :setup, :teardown
-
end
-
-
1
module ClassMethods
-
# Add a callback, which runs before <tt>TestCase#setup</tt>.
-
1
def setup(*args, &block)
-
7
set_callback(:setup, :before, *args, &block)
-
end
-
-
# Add a callback, which runs after <tt>TestCase#teardown</tt>.
-
1
def teardown(*args, &block)
-
4
set_callback(:teardown, :after, *args, &block)
-
end
-
end
-
-
1
def before_setup # :nodoc:
-
super
-
run_callbacks :setup
-
end
-
-
1
def after_teardown # :nodoc:
-
run_callbacks :teardown
-
super
-
end
-
end
-
end
-
end
-
1
module ActiveSupport
-
1
module Testing
-
# Logs a "PostsControllerTest: test name" heading before each test to
-
# make test.log easier to search and follow along with.
-
1
module TaggedLogging #:nodoc:
-
1
attr_writer :tagged_logger
-
-
1
def before_setup
-
if tagged_logger && tagged_logger.info?
-
heading = "#{self.class}: #{name}"
-
divider = '-' * heading.size
-
tagged_logger.info divider
-
tagged_logger.info heading
-
tagged_logger.info divider
-
end
-
super
-
end
-
-
1
private
-
1
def tagged_logger
-
@tagged_logger ||= (defined?(Rails.logger) && Rails.logger)
-
end
-
end
-
end
-
end
-
1
module ActiveSupport
-
1
module Testing
-
1
class SimpleStubs # :nodoc:
-
1
Stub = Struct.new(:object, :method_name, :original_method)
-
-
1
def initialize
-
@stubs = {}
-
end
-
-
1
def stub_object(object, method_name, return_value)
-
key = [object.object_id, method_name]
-
-
if stub = @stubs[key]
-
unstub_object(stub)
-
end
-
-
new_name = "__simple_stub__#{method_name}"
-
-
@stubs[key] = Stub.new(object, method_name, new_name)
-
-
object.singleton_class.send :alias_method, new_name, method_name
-
object.define_singleton_method(method_name) { return_value }
-
end
-
-
1
def unstub_all!
-
@stubs.each_value do |stub|
-
unstub_object(stub)
-
end
-
@stubs = {}
-
end
-
-
1
private
-
-
1
def unstub_object(stub)
-
singleton_class = stub.object.singleton_class
-
singleton_class.send :undef_method, stub.method_name
-
singleton_class.send :alias_method, stub.method_name, stub.original_method
-
singleton_class.send :undef_method, stub.original_method
-
end
-
end
-
-
# Containing helpers that helps you test passage of time.
-
1
module TimeHelpers
-
# Changes current time to the time in the future or in the past by a given time difference by
-
# stubbing +Time.now+ and +Date.today+.
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# travel 1.day
-
# Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00
-
# Date.current # => Sun, 10 Nov 2013
-
#
-
# This method also accepts a block, which will return the current time back to its original
-
# state at the end of the block:
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# travel 1.day do
-
# User.create.created_at # => Sun, 10 Nov 2013 15:34:49 EST -05:00
-
# end
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
1
def travel(duration, &block)
-
travel_to Time.now + duration, &block
-
end
-
-
# Changes current time to the given time by stubbing +Time.now+ and
-
# +Date.today+ to return the time or date passed into this method.
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# travel_to Time.new(2004, 11, 24, 01, 04, 44)
-
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
-
# Date.current # => Wed, 24 Nov 2004
-
#
-
# Dates are taken as their timestamp at the beginning of the day in the
-
# application time zone. <tt>Time.current</tt> returns said timestamp,
-
# and <tt>Time.now</tt> its equivalent in the system time zone. Similarly,
-
# <tt>Date.current</tt> returns a date equal to the argument, and
-
# <tt>Date.today</tt> the date according to <tt>Time.now</tt>, which may
-
# be different. (Note that you rarely want to deal with <tt>Time.now</tt>,
-
# or <tt>Date.today</tt>, in order to honor the application time zone
-
# please always use <tt>Time.current</tt> and <tt>Date.current</tt>.)
-
#
-
# Note that the usec for the time passed will be set to 0 to prevent rounding
-
# errors with external services, like MySQL (which will round instead of floor,
-
# leading to off-by-one-second errors).
-
#
-
# This method also accepts a block, which will return the current time back to its original
-
# state at the end of the block:
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# travel_to Time.new(2004, 11, 24, 01, 04, 44) do
-
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
-
# end
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
1
def travel_to(date_or_time)
-
if date_or_time.is_a?(Date) && !date_or_time.is_a?(DateTime)
-
now = date_or_time.midnight.to_time
-
else
-
now = date_or_time.to_time.change(usec: 0)
-
end
-
-
simple_stubs.stub_object(Time, :now, now)
-
simple_stubs.stub_object(Date, :today, now.to_date)
-
-
if block_given?
-
begin
-
yield
-
ensure
-
travel_back
-
end
-
end
-
end
-
-
# Returns the current time back to its original state, by removing the stubs added by
-
# `travel` and `travel_to`.
-
#
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
# travel_to Time.new(2004, 11, 24, 01, 04, 44)
-
# Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00
-
# travel_back
-
# Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00
-
1
def travel_back
-
simple_stubs.unstub_all!
-
end
-
-
1
private
-
-
1
def simple_stubs
-
@simple_stubs ||= SimpleStubs.new
-
end
-
end
-
end
-
end
-
# A Ruby library implementing OpenBSD's bcrypt()/crypt_blowfish algorithm for
-
# hashing passwords.
-
1
module BCrypt
-
end
-
-
1
if RUBY_PLATFORM == "java"
-
require 'java'
-
else
-
1
require "openssl"
-
end
-
-
1
begin
-
1
RUBY_VERSION =~ /(\d+.\d+)/
-
1
require "#{$1}/bcrypt_ext"
-
rescue LoadError
-
1
require "bcrypt_ext"
-
end
-
-
1
require 'bcrypt/error'
-
1
require 'bcrypt/engine'
-
1
require 'bcrypt/password'
-
1
module BCrypt
-
# A Ruby wrapper for the bcrypt() C extension calls and the Java calls.
-
1
class Engine
-
# The default computational expense parameter.
-
1
DEFAULT_COST = 10
-
# The minimum cost supported by the algorithm.
-
1
MIN_COST = 4
-
# Maximum possible size of bcrypt() salts.
-
1
MAX_SALT_LENGTH = 16
-
-
1
if RUBY_PLATFORM != "java"
-
# C-level routines which, if they don't get the right input, will crash the
-
# hell out of the Ruby process.
-
1
private_class_method :__bc_salt
-
1
private_class_method :__bc_crypt
-
end
-
-
1
@cost = nil
-
-
# Returns the cost factor that will be used if one is not specified when
-
# creating a password hash. Defaults to DEFAULT_COST if not set.
-
1
def self.cost
-
@cost || DEFAULT_COST
-
end
-
-
# Set a default cost factor that will be used if one is not specified when
-
# creating a password hash.
-
#
-
# Example:
-
#
-
# BCrypt::Engine::DEFAULT_COST #=> 10
-
# BCrypt::Password.create('secret').cost #=> 10
-
#
-
# BCrypt::Engine.cost = 8
-
# BCrypt::Password.create('secret').cost #=> 8
-
#
-
# # cost can still be overridden as needed
-
# BCrypt::Password.create('secret', :cost => 6).cost #=> 6
-
1
def self.cost=(cost)
-
@cost = cost
-
end
-
-
# Given a secret and a valid salt (see BCrypt::Engine.generate_salt) calculates
-
# a bcrypt() password hash.
-
1
def self.hash_secret(secret, salt, _ = nil)
-
21
if valid_secret?(secret)
-
21
if valid_salt?(salt)
-
21
if RUBY_PLATFORM == "java"
-
Java.bcrypt_jruby.BCrypt.hashpw(secret.to_s, salt.to_s)
-
else
-
21
__bc_crypt(secret.to_s, salt)
-
end
-
else
-
raise Errors::InvalidSalt.new("invalid salt")
-
end
-
else
-
raise Errors::InvalidSecret.new("invalid secret")
-
end
-
end
-
-
# Generates a random salt with a given computational cost.
-
1
def self.generate_salt(cost = self.cost)
-
21
cost = cost.to_i
-
21
if cost > 0
-
21
if cost < MIN_COST
-
21
cost = MIN_COST
-
end
-
21
if RUBY_PLATFORM == "java"
-
Java.bcrypt_jruby.BCrypt.gensalt(cost)
-
else
-
21
prefix = "$2a$05$CCCCCCCCCCCCCCCCCCCCC.E5YPO9kmyuRGyh0XouQYb4YMJKvyOeW"
-
21
__bc_salt(prefix, cost, OpenSSL::Random.random_bytes(MAX_SALT_LENGTH))
-
end
-
else
-
raise Errors::InvalidCost.new("cost must be numeric and > 0")
-
end
-
end
-
-
# Returns true if +salt+ is a valid bcrypt() salt, false if not.
-
1
def self.valid_salt?(salt)
-
21
!!(salt =~ /^\$[0-9a-z]{2,}\$[0-9]{2,}\$[A-Za-z0-9\.\/]{22,}$/)
-
end
-
-
# Returns true if +secret+ is a valid bcrypt() secret, false if not.
-
1
def self.valid_secret?(secret)
-
21
secret.respond_to?(:to_s)
-
end
-
-
# Returns the cost factor which will result in computation times less than +upper_time_limit_in_ms+.
-
#
-
# Example:
-
#
-
# BCrypt::Engine.calibrate(200) #=> 10
-
# BCrypt::Engine.calibrate(1000) #=> 12
-
#
-
# # should take less than 200ms
-
# BCrypt::Password.create("woo", :cost => 10)
-
#
-
# # should take less than 1000ms
-
# BCrypt::Password.create("woo", :cost => 12)
-
1
def self.calibrate(upper_time_limit_in_ms)
-
40.times do |i|
-
start_time = Time.now
-
Password.create("testing testing", :cost => i+1)
-
end_time = Time.now - start_time
-
return i if end_time * 1_000 > upper_time_limit_in_ms
-
end
-
end
-
-
# Autodetects the cost from the salt string.
-
1
def self.autodetect_cost(salt)
-
salt[4..5].to_i
-
end
-
end
-
-
end
-
1
module BCrypt
-
-
1
class Error < StandardError # :nodoc:
-
end
-
-
1
module Errors # :nodoc:
-
-
# The salt parameter provided to bcrypt() is invalid.
-
1
class InvalidSalt < BCrypt::Error; end
-
-
# The hash parameter provided to bcrypt() is invalid.
-
1
class InvalidHash < BCrypt::Error; end
-
-
# The cost parameter provided to bcrypt() is invalid.
-
1
class InvalidCost < BCrypt::Error; end
-
-
# The secret parameter provided to bcrypt() is invalid.
-
1
class InvalidSecret < BCrypt::Error; end
-
-
end
-
-
end
-
1
module BCrypt
-
# A password management class which allows you to safely store users' passwords and compare them.
-
#
-
# Example usage:
-
#
-
# include BCrypt
-
#
-
# # hash a user's password
-
# @password = Password.create("my grand secret")
-
# @password #=> "$2a$10$GtKs1Kbsig8ULHZzO1h2TetZfhO4Fmlxphp8bVKnUlZCBYYClPohG"
-
#
-
# # store it safely
-
# @user.update_attribute(:password, @password)
-
#
-
# # read it back
-
# @user.reload!
-
# @db_password = Password.new(@user.password)
-
#
-
# # compare it after retrieval
-
# @db_password == "my grand secret" #=> true
-
# @db_password == "a paltry guess" #=> false
-
#
-
1
class Password < String
-
# The hash portion of the stored password hash.
-
1
attr_reader :checksum
-
# The salt of the store password hash (including version and cost).
-
1
attr_reader :salt
-
# The version of the bcrypt() algorithm used to create the hash.
-
1
attr_reader :version
-
# The cost factor used to create the hash.
-
1
attr_reader :cost
-
-
1
class << self
-
# Hashes a secret, returning a BCrypt::Password instance. Takes an optional <tt>:cost</tt> option, which is a
-
# logarithmic variable which determines how computational expensive the hash is to calculate (a <tt>:cost</tt> of
-
# 4 is twice as much work as a <tt>:cost</tt> of 3). The higher the <tt>:cost</tt> the harder it becomes for
-
# attackers to try to guess passwords (even if a copy of your database is stolen), but the slower it is to check
-
# users' passwords.
-
#
-
# Example:
-
#
-
# @password = BCrypt::Password.create("my secret", :cost => 13)
-
1
def create(secret, options = {})
-
21
cost = options[:cost] || BCrypt::Engine.cost
-
21
raise ArgumentError if cost > 31
-
21
Password.new(BCrypt::Engine.hash_secret(secret, BCrypt::Engine.generate_salt(cost)))
-
end
-
-
1
def valid_hash?(h)
-
21
h =~ /^\$[0-9a-z]{2}\$[0-9]{2}\$[A-Za-z0-9\.\/]{53}$/
-
end
-
end
-
-
# Initializes a BCrypt::Password instance with the data from a stored hash.
-
1
def initialize(raw_hash)
-
21
if valid_hash?(raw_hash)
-
21
self.replace(raw_hash)
-
21
@version, @cost, @salt, @checksum = split_hash(self)
-
else
-
raise Errors::InvalidHash.new("invalid hash")
-
end
-
end
-
-
# Compares a potential secret against the hash. Returns true if the secret is the original secret, false otherwise.
-
1
def ==(secret)
-
super(BCrypt::Engine.hash_secret(secret, @salt))
-
end
-
1
alias_method :is_password?, :==
-
-
1
private
-
-
# Returns true if +h+ is a valid hash.
-
1
def valid_hash?(h)
-
21
self.class.valid_hash?(h)
-
end
-
-
# call-seq:
-
# split_hash(raw_hash) -> version, cost, salt, hash
-
#
-
# Splits +h+ into version, cost, salt, and hash and returns them in that order.
-
1
def split_hash(h)
-
21
_, v, c, mash = h.split('$')
-
21
return v.to_str, c.to_i, h[0, 29].to_str, mash[-31, 31].to_str
-
end
-
end
-
-
end
-
1
module DeviseHelper
-
# A simple way to show error messages for the current devise resource. If you need
-
# to customize this method, you can either overwrite it in your application helpers or
-
# copy the views to your application.
-
#
-
# This method is intended to stay simple and it is unlikely that we are going to change
-
# it to add more behavior or options.
-
1
def devise_error_messages!
-
return "" if resource.errors.empty?
-
-
messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
-
sentence = I18n.t("errors.messages.not_saved",
-
count: resource.errors.count,
-
resource: resource.class.model_name.human.downcase)
-
-
html = <<-HTML
-
<div id="error_explanation">
-
<h2>#{sentence}</h2>
-
<ul>#{messages}</ul>
-
</div>
-
HTML
-
-
html.html_safe
-
end
-
end
-
1
require 'bcrypt'
-
-
1
module Devise
-
1
module Encryptor
-
1
def self.digest(klass, password)
-
21
if klass.pepper.present?
-
password = "#{password}#{klass.pepper}"
-
end
-
21
::BCrypt::Password.create(password, cost: klass.stretches).to_s
-
end
-
-
1
def self.compare(klass, encrypted_password, password)
-
return false if encrypted_password.blank?
-
bcrypt = ::BCrypt::Password.new(encrypted_password)
-
if klass.pepper.present?
-
password = "#{password}#{klass.pepper}"
-
end
-
password = ::BCrypt::Engine.hash_secret(password, bcrypt.salt)
-
Devise.secure_compare(password, encrypted_password)
-
end
-
end
-
end
-
1
module Devise
-
# Devise::TestHelpers provides a facility to test controllers in isolation
-
# when using ActionController::TestCase allowing you to quickly sign_in or
-
# sign_out a user. Do not use Devise::TestHelpers in integration tests.
-
#
-
# Notice you should not test Warden specific behavior (like Warden callbacks)
-
# using Devise::TestHelpers since it is a stub of the actual behavior. Such
-
# callbacks should be tested in your integration suite instead.
-
1
module TestHelpers
-
1
def self.included(base)
-
1
base.class_eval do
-
1
setup :setup_controller_for_warden, :warden if respond_to?(:setup)
-
end
-
end
-
-
# Override process to consider warden.
-
1
def process(*)
-
# Make sure we always return @response, a la ActionController::TestCase::Behaviour#process, even if warden interrupts
-
46
_catch_warden { super } || @response
-
end
-
-
# We need to setup the environment variables and the response in the controller.
-
1
def setup_controller_for_warden #:nodoc:
-
7
@request.env['action_controller.instance'] = @controller
-
end
-
-
# Quick access to Warden::Proxy.
-
1
def warden #:nodoc:
-
25
@request.env['warden'] ||= begin
-
7
manager = Warden::Manager.new(nil) do |config|
-
7
config.merge! Devise.warden_config
-
end
-
7
Warden::Proxy.new(@request.env, manager)
-
end
-
end
-
-
# sign_in a given resource by storing its keys in the session.
-
# This method bypass any warden authentication callback.
-
#
-
# Examples:
-
#
-
# sign_in :user, @user # sign_in(scope, resource)
-
# sign_in @user # sign_in(resource)
-
#
-
1
def sign_in(resource_or_scope, resource=nil)
-
8
scope ||= Devise::Mapping.find_scope!(resource_or_scope)
-
8
resource ||= resource_or_scope
-
8
warden.instance_variable_get(:@users).delete(scope)
-
8
warden.session_serializer.store(resource, scope)
-
end
-
-
# Sign out a given resource or scope by calling logout on Warden.
-
# This method bypass any warden logout callback.
-
#
-
# Examples:
-
#
-
# sign_out :user # sign_out(scope)
-
# sign_out @user # sign_out(resource)
-
#
-
1
def sign_out(resource_or_scope)
-
1
scope = Devise::Mapping.find_scope!(resource_or_scope)
-
1
@controller.instance_variable_set(:"@current_#{scope}", nil)
-
1
user = warden.instance_variable_get(:@users).delete(scope)
-
1
warden.session_serializer.delete(scope, user)
-
end
-
-
1
protected
-
-
# Catch warden continuations and handle like the middleware would.
-
# Returns nil when interrupted, otherwise the normal result of the block.
-
1
def _catch_warden(&block)
-
23
result = catch(:warden, &block)
-
-
23
env = @controller.request.env
-
-
23
result ||= {}
-
-
# Set the response. In production, the rack result is returned
-
# from Warden::Manager#call, which the following is modelled on.
-
23
case result
-
when Array
-
if result.first == 401 && intercept_401?(env) # does this happen during testing?
-
_process_unauthenticated(env)
-
else
-
result
-
end
-
when Hash
-
_process_unauthenticated(env, result)
-
else
-
23
result
-
end
-
end
-
-
1
def _process_unauthenticated(env, options = {})
-
options[:action] ||= :unauthenticated
-
proxy = env['warden']
-
result = options[:result] || proxy.result
-
-
ret = case result
-
when :redirect
-
body = proxy.message || "You are being redirected to #{proxy.headers['Location']}"
-
[proxy.status, proxy.headers, [body]]
-
when :custom
-
proxy.custom_response
-
else
-
env["PATH_INFO"] = "/#{options[:action]}"
-
env["warden.options"] = options
-
Warden::Manager._run_callbacks(:before_failure, env, options)
-
-
status, headers, response = Devise.warden_config[:failure_app].call(env).to_a
-
@controller.response.headers.merge!(headers)
-
@controller.send :render, status: status, text: response.body,
-
content_type: headers["Content-Type"], location: headers["Location"]
-
nil # causes process return @response
-
end
-
-
# ensure that the controller response is set up. In production, this is
-
# not necessary since warden returns the results to rack. However, at
-
# testing time, we want the response to be available to the testing
-
# framework to verify what would be returned to rack.
-
if ret.is_a?(Array)
-
# ensure the controller response is set to our response.
-
@controller.response ||= @response
-
@response.status = ret.first
-
@response.headers = ret.second
-
@response.body = ret.third
-
end
-
-
ret
-
end
-
end
-
end
-
1
module Geocoder
-
1
module Sql
-
1
extend self
-
-
##
-
# Distance calculation for use with a database that supports POWER(),
-
# SQRT(), PI(), and trigonometric functions SIN(), COS(), ASIN(),
-
# ATAN2(), DEGREES(), and RADIANS().
-
#
-
# Based on the excellent tutorial at:
-
# http://www.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL
-
#
-
1
def full_distance(latitude, longitude, lat_attr, lon_attr, options = {})
-
units = options[:units] || Geocoder.config.units
-
earth = Geocoder::Calculations.earth_radius(units)
-
-
"#{earth} * 2 * ASIN(SQRT(" +
-
"POWER(SIN((#{latitude.to_f} - #{lat_attr}) * PI() / 180 / 2), 2) + " +
-
"COS(#{latitude.to_f} * PI() / 180) * COS(#{lat_attr} * PI() / 180) * " +
-
"POWER(SIN((#{longitude.to_f} - #{lon_attr}) * PI() / 180 / 2), 2)" +
-
"))"
-
end
-
-
##
-
# Distance calculation for use with a database without trigonometric
-
# functions, like SQLite. Approach is to find objects within a square
-
# rather than a circle, so results are very approximate (will include
-
# objects outside the given radius).
-
#
-
# Distance and bearing calculations are *extremely inaccurate*. To be
-
# clear: this only exists to provide interface consistency. Results
-
# are not intended for use in production!
-
#
-
1
def approx_distance(latitude, longitude, lat_attr, lon_attr, options = {})
-
units = options[:units] || Geocoder.config.units
-
dx = Geocoder::Calculations.longitude_degree_distance(30, units)
-
dy = Geocoder::Calculations.latitude_degree_distance(units)
-
-
# sin of 45 degrees = average x or y component of vector
-
factor = Math.sin(Math::PI / 4)
-
-
"(#{dy} * ABS(#{lat_attr} - #{latitude.to_f}) * #{factor}) + " +
-
"(#{dx} * ABS(#{lon_attr} - #{longitude.to_f}) * #{factor})"
-
end
-
-
1
def within_bounding_box(sw_lat, sw_lng, ne_lat, ne_lng, lat_attr, lon_attr)
-
spans = "#{lat_attr} BETWEEN #{sw_lat} AND #{ne_lat} AND "
-
# handle box that spans 180 longitude
-
if sw_lng.to_f > ne_lng.to_f
-
spans + "#{lon_attr} BETWEEN #{sw_lng} AND 180 OR " +
-
"#{lon_attr} BETWEEN -180 AND #{ne_lng}"
-
else
-
spans + "#{lon_attr} BETWEEN #{sw_lng} AND #{ne_lng}"
-
end
-
end
-
-
##
-
# Fairly accurate bearing calculation. Takes a latitude, longitude,
-
# and an options hash which must include a :bearing value
-
# (:linear or :spherical).
-
#
-
# Based on:
-
# http://www.beginningspatial.com/calculating_bearing_one_point_another
-
#
-
1
def full_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
-
degrees_per_radian = Geocoder::Calculations::DEGREES_PER_RADIAN
-
case options[:bearing] || Geocoder.config.distances
-
when :linear
-
"MOD(CAST(" +
-
"(ATAN2( " +
-
"((#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian}), " +
-
"((#{lat_attr} - #{latitude.to_f}) / #{degrees_per_radian})" +
-
") * #{degrees_per_radian}) + 360 " +
-
"AS decimal), 360)"
-
when :spherical
-
"MOD(CAST(" +
-
"(ATAN2( " +
-
"SIN( (#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian} ) * " +
-
"COS( (#{lat_attr}) / #{degrees_per_radian} ), (" +
-
"COS( (#{latitude.to_f}) / #{degrees_per_radian} ) * SIN( (#{lat_attr}) / #{degrees_per_radian})" +
-
") - (" +
-
"SIN( (#{latitude.to_f}) / #{degrees_per_radian}) * COS((#{lat_attr}) / #{degrees_per_radian}) * " +
-
"COS( (#{lon_attr} - #{longitude.to_f}) / #{degrees_per_radian})" +
-
")" +
-
") * #{degrees_per_radian}) + 360 " +
-
"AS decimal), 360)"
-
end
-
end
-
-
##
-
# Totally lame bearing calculation. Basically useless except that it
-
# returns *something* in databases without trig functions.
-
#
-
1
def approx_bearing(latitude, longitude, lat_attr, lon_attr, options = {})
-
"CASE " +
-
"WHEN (#{lat_attr} >= #{latitude.to_f} AND " +
-
"#{lon_attr} >= #{longitude.to_f}) THEN 45.0 " +
-
"WHEN (#{lat_attr} < #{latitude.to_f} AND " +
-
"#{lon_attr} >= #{longitude.to_f}) THEN 135.0 " +
-
"WHEN (#{lat_attr} < #{latitude.to_f} AND " +
-
"#{lon_attr} < #{longitude.to_f}) THEN 225.0 " +
-
"WHEN (#{lat_attr} >= #{latitude.to_f} AND " +
-
"#{lon_attr} < #{longitude.to_f}) THEN 315.0 " +
-
"END"
-
end
-
end
-
end
-
# -*- coding: utf-8 -*-
-
1
require 'geocoder/sql'
-
1
require 'geocoder/stores/base'
-
-
##
-
# Add geocoding functionality to any ActiveRecord object.
-
#
-
1
module Geocoder::Store
-
1
module ActiveRecord
-
1
include Base
-
-
##
-
# Implementation of 'included' hook method.
-
#
-
1
def self.included(base)
-
1
base.extend ClassMethods
-
1
base.class_eval do
-
-
# scope: geocoded objects
-
1
scope :geocoded, lambda {
-
where("#{table_name}.#{geocoder_options[:latitude]} IS NOT NULL " +
-
"AND #{table_name}.#{geocoder_options[:longitude]} IS NOT NULL")
-
}
-
-
# scope: not-geocoded objects
-
1
scope :not_geocoded, lambda {
-
where("#{table_name}.#{geocoder_options[:latitude]} IS NULL " +
-
"OR #{table_name}.#{geocoder_options[:longitude]} IS NULL")
-
}
-
-
##
-
# Find all objects within a radius of the given location.
-
# Location may be either a string to geocode or an array of
-
# coordinates (<tt>[lat,lon]</tt>). Also takes an options hash
-
# (see Geocoder::Store::ActiveRecord::ClassMethods.near_scope_options
-
# for details).
-
#
-
1
scope :near, lambda{ |location, *args|
-
latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
-
if Geocoder::Calculations.coordinates_present?(latitude, longitude)
-
options = near_scope_options(latitude, longitude, *args)
-
select(options[:select]).where(options[:conditions]).
-
order(options[:order])
-
else
-
# If no lat/lon given we don't want any results, but we still
-
# need distance and bearing columns so you can add, for example:
-
# .order("distance")
-
select(select_clause(nil, null_value, null_value)).where(false_condition)
-
end
-
}
-
-
##
-
# Find all objects within the area of a given bounding box.
-
# Bounds must be an array of locations specifying the southwest
-
# corner followed by the northeast corner of the box
-
# (<tt>[[sw_lat, sw_lon], [ne_lat, ne_lon]]</tt>).
-
#
-
1
scope :within_bounding_box, lambda{ |bounds|
-
sw_lat, sw_lng, ne_lat, ne_lng = bounds.flatten if bounds
-
if sw_lat && sw_lng && ne_lat && ne_lng
-
where(Geocoder::Sql.within_bounding_box(
-
sw_lat, sw_lng, ne_lat, ne_lng,
-
full_column_name(geocoder_options[:latitude]),
-
full_column_name(geocoder_options[:longitude])
-
))
-
else
-
select(select_clause(nil, null_value, null_value)).where(false_condition)
-
end
-
}
-
end
-
end
-
-
##
-
# Methods which will be class methods of the including class.
-
#
-
1
module ClassMethods
-
-
1
def distance_from_sql(location, *args)
-
latitude, longitude = Geocoder::Calculations.extract_coordinates(location)
-
if Geocoder::Calculations.coordinates_present?(latitude, longitude)
-
distance_sql(latitude, longitude, *args)
-
end
-
end
-
-
1
private # ----------------------------------------------------------------
-
-
##
-
# Get options hash suitable for passing to ActiveRecord.find to get
-
# records within a radius (in kilometers) of the given point.
-
# Options hash may include:
-
#
-
# * +:units+ - <tt>:mi</tt> or <tt>:km</tt>; to be used.
-
# for interpreting radius as well as the +distance+ attribute which
-
# is added to each found nearby object.
-
# Use Geocoder.configure[:units] to configure default units.
-
# * +:bearing+ - <tt>:linear</tt> or <tt>:spherical</tt>.
-
# the method to be used for calculating the bearing (direction)
-
# between the given point and each found nearby point;
-
# set to false for no bearing calculation. Use
-
# Geocoder.configure[:distances] to configure default calculation method.
-
# * +:select+ - string with the SELECT SQL fragment (e.g. “id, name”)
-
# * +:select_distance+ - whether to include the distance alias in the
-
# SELECT SQL fragment (e.g. <formula> AS distance)
-
# * +:select_bearing+ - like +:select_distance+ but for bearing.
-
# * +:order+ - column(s) for ORDER BY SQL clause; default is distance;
-
# set to false or nil to omit the ORDER BY clause
-
# * +:exclude+ - an object to exclude (used by the +nearbys+ method)
-
# * +:distance_column+ - used to set the column name of the calculated distance.
-
# * +:bearing_column+ - used to set the column name of the calculated bearing.
-
# * +:min_radius+ - the value to use as the minimum radius.
-
# ignored if database is sqlite.
-
# default is 0.0
-
#
-
1
def near_scope_options(latitude, longitude, radius = 20, options = {})
-
if options[:units]
-
options[:units] = options[:units].to_sym
-
end
-
latitude_attribute = options[:latitude] || geocoder_options[:latitude]
-
longitude_attribute = options[:longitude] || geocoder_options[:longitude]
-
options[:units] ||= (geocoder_options[:units] || Geocoder.config.units)
-
select_distance = options.fetch(:select_distance) { true }
-
options[:order] = "" if !select_distance && !options.include?(:order)
-
select_bearing = options.fetch(:select_bearing) { true }
-
bearing = bearing_sql(latitude, longitude, options)
-
distance = distance_sql(latitude, longitude, options)
-
distance_column = options.fetch(:distance_column) { 'distance' }
-
bearing_column = options.fetch(:bearing_column) { 'bearing' }
-
-
b = Geocoder::Calculations.bounding_box([latitude, longitude], radius, options)
-
args = b + [
-
full_column_name(latitude_attribute),
-
full_column_name(longitude_attribute)
-
]
-
bounding_box_conditions = Geocoder::Sql.within_bounding_box(*args)
-
-
if using_sqlite?
-
conditions = bounding_box_conditions
-
else
-
min_radius = options.fetch(:min_radius, 0).to_f
-
conditions = [bounding_box_conditions + " AND (#{distance}) BETWEEN ? AND ?", min_radius, radius]
-
end
-
{
-
:select => select_clause(options[:select],
-
select_distance ? distance : nil,
-
select_bearing ? bearing : nil,
-
distance_column,
-
bearing_column),
-
:conditions => add_exclude_condition(conditions, options[:exclude]),
-
:order => options.include?(:order) ? options[:order] : "#{distance_column} ASC"
-
}
-
end
-
-
##
-
# SQL for calculating distance based on the current database's
-
# capabilities (trig functions?).
-
#
-
1
def distance_sql(latitude, longitude, options = {})
-
method_prefix = using_sqlite? ? "approx" : "full"
-
Geocoder::Sql.send(
-
method_prefix + "_distance",
-
latitude, longitude,
-
full_column_name(options[:latitude] || geocoder_options[:latitude]),
-
full_column_name(options[:longitude]|| geocoder_options[:longitude]),
-
options
-
)
-
end
-
-
##
-
# SQL for calculating bearing based on the current database's
-
# capabilities (trig functions?).
-
#
-
1
def bearing_sql(latitude, longitude, options = {})
-
if !options.include?(:bearing)
-
options[:bearing] = Geocoder.config.distances
-
end
-
if options[:bearing]
-
method_prefix = using_sqlite? ? "approx" : "full"
-
Geocoder::Sql.send(
-
method_prefix + "_bearing",
-
latitude, longitude,
-
full_column_name(options[:latitude] || geocoder_options[:latitude]),
-
full_column_name(options[:longitude]|| geocoder_options[:longitude]),
-
options
-
)
-
end
-
end
-
-
##
-
# Generate the SELECT clause.
-
#
-
1
def select_clause(columns, distance = nil, bearing = nil, distance_column = 'distance', bearing_column = 'bearing')
-
if columns == :id_only
-
return full_column_name(primary_key)
-
elsif columns == :geo_only
-
clause = ""
-
else
-
clause = (columns || full_column_name("*"))
-
end
-
if distance
-
clause += ", " unless clause.empty?
-
clause += "#{distance} AS #{distance_column}"
-
end
-
if bearing
-
clause += ", " unless clause.empty?
-
clause += "#{bearing} AS #{bearing_column}"
-
end
-
clause
-
end
-
-
##
-
# Adds a condition to exclude a given object by ID.
-
# Expects conditions as an array or string. Returns array.
-
#
-
1
def add_exclude_condition(conditions, exclude)
-
conditions = [conditions] if conditions.is_a?(String)
-
if exclude
-
conditions[0] << " AND #{full_column_name(primary_key)} != ?"
-
conditions << exclude.id
-
end
-
conditions
-
end
-
-
1
def using_sqlite?
-
connection.adapter_name.match(/sqlite/i)
-
end
-
-
1
def using_postgres?
-
connection.adapter_name.match(/postgres/i)
-
end
-
-
##
-
# Use OID type when running in PosgreSQL
-
#
-
1
def null_value
-
using_postgres? ? 'NULL::text' : 'NULL'
-
end
-
-
##
-
# Value which can be passed to where() to produce no results.
-
#
-
1
def false_condition
-
using_sqlite? ? 0 : "false"
-
end
-
-
##
-
# Prepend table name if column name doesn't already contain one.
-
#
-
1
def full_column_name(column)
-
column = column.to_s
-
column.include?(".") ? column : [table_name, column].join(".")
-
end
-
end
-
-
##
-
# Get nearby geocoded objects.
-
# Takes the same options hash as the near class method (scope).
-
# Returns nil if the object is not geocoded.
-
#
-
1
def nearbys(radius = 20, options = {})
-
return nil unless geocoded?
-
options.merge!(:exclude => self) unless send(self.class.primary_key).nil?
-
self.class.near(self, radius, options)
-
end
-
-
##
-
# Look up coordinates and assign to +latitude+ and +longitude+ attributes
-
# (or other as specified in +geocoded_by+). Returns coordinates (array).
-
#
-
1
def geocode
-
16
do_lookup(false) do |o,rs|
-
16
if r = rs.first
-
16
unless r.latitude.nil? or r.longitude.nil?
-
16
o.__send__ "#{self.class.geocoder_options[:latitude]}=", r.latitude
-
16
o.__send__ "#{self.class.geocoder_options[:longitude]}=", r.longitude
-
end
-
16
r.coordinates
-
end
-
end
-
end
-
-
1
alias_method :fetch_coordinates, :geocode
-
-
##
-
# Look up address and assign to +address+ attribute (or other as specified
-
# in +reverse_geocoded_by+). Returns address (string).
-
#
-
1
def reverse_geocode
-
do_lookup(true) do |o,rs|
-
if r = rs.first
-
unless r.address.nil?
-
o.__send__ "#{self.class.geocoder_options[:fetched_address]}=", r.address
-
end
-
r.address
-
end
-
end
-
end
-
-
1
alias_method :fetch_address, :reverse_geocode
-
end
-
end
-
1
module Geocoder
-
1
module Store
-
1
module Base
-
-
##
-
# Is this object geocoded? (Does it have latitude and longitude?)
-
#
-
1
def geocoded?
-
to_coordinates.compact.size > 0
-
end
-
-
##
-
# Coordinates [lat,lon] of the object.
-
#
-
1
def to_coordinates
-
[:latitude, :longitude].map{ |i| send self.class.geocoder_options[i] }
-
end
-
-
##
-
# Calculate the distance from the object to an arbitrary point.
-
# See Geocoder::Calculations.distance_between for ways of specifying
-
# the point. Also takes a symbol specifying the units
-
# (:mi or :km; can be specified in Geocoder configuration).
-
#
-
1
def distance_to(point, units = nil)
-
units ||= self.class.geocoder_options[:units]
-
return nil unless geocoded?
-
Geocoder::Calculations.distance_between(
-
to_coordinates, point, :units => units)
-
end
-
-
1
alias_method :distance_from, :distance_to
-
-
##
-
# Calculate the bearing from the object to another point.
-
# See Geocoder::Calculations.distance_between for
-
# ways of specifying the point.
-
#
-
1
def bearing_to(point, options = {})
-
options[:method] ||= self.class.geocoder_options[:method]
-
return nil unless geocoded?
-
Geocoder::Calculations.bearing_between(
-
to_coordinates, point, options)
-
end
-
-
##
-
# Calculate the bearing from another point to the object.
-
# See Geocoder::Calculations.distance_between for
-
# ways of specifying the point.
-
#
-
1
def bearing_from(point, options = {})
-
options[:method] ||= self.class.geocoder_options[:method]
-
return nil unless geocoded?
-
Geocoder::Calculations.bearing_between(
-
point, to_coordinates, options)
-
end
-
-
##
-
# Look up coordinates and assign to +latitude+ and +longitude+ attributes
-
# (or other as specified in +geocoded_by+). Returns coordinates (array).
-
#
-
1
def geocode
-
fail
-
end
-
-
##
-
# Look up address and assign to +address+ attribute (or other as specified
-
# in +reverse_geocoded_by+). Returns address (string).
-
#
-
1
def reverse_geocode
-
fail
-
end
-
-
1
private # --------------------------------------------------------------
-
-
##
-
# Look up geographic data based on object attributes (configured in
-
# geocoded_by or reverse_geocoded_by) and handle the results with the
-
# block (given to geocoded_by or reverse_geocoded_by). The block is
-
# given two-arguments: the object being geocoded and an array of
-
# Geocoder::Result objects).
-
#
-
1
def do_lookup(reverse = false)
-
16
options = self.class.geocoder_options
-
16
if reverse and options[:reverse_geocode]
-
query = to_coordinates
-
elsif !reverse and options[:geocode]
-
16
query = send(options[:user_address])
-
else
-
return
-
end
-
-
16
query_options = [:lookup, :ip_lookup, :language].inject({}) do |hash, key|
-
48
if options.has_key?(key)
-
32
val = options[key]
-
32
hash[key] = val.respond_to?(:call) ? val.call(self) : val
-
end
-
48
hash
-
end
-
16
results = Geocoder.search(query, query_options)
-
-
# execute custom block, if specified in configuration
-
16
block_key = reverse ? :reverse_block : :geocode_block
-
16
if custom_block = options[block_key]
-
custom_block.call(self, results)
-
-
# else execute block passed directly to this method,
-
# which generally performs the "auto-assigns"
-
16
elsif block_given?
-
16
yield(self, results)
-
end
-
end
-
end
-
end
-
end
-
-
1
require "optparse"
-
1
require "thread"
-
1
require "mutex_m"
-
1
require "minitest/parallel"
-
-
##
-
# :include: README.rdoc
-
-
1
module Minitest
-
1
VERSION = "5.8.2" # :nodoc:
-
1
ENCS = "".respond_to? :encoding # :nodoc:
-
-
1
@@installed_at_exit ||= false
-
1
@@after_run = []
-
1
@extensions = []
-
-
2
mc = (class << self; self; end)
-
-
##
-
# Parallel test executor
-
-
1
mc.send :attr_accessor, :parallel_executor
-
1
self.parallel_executor = Parallel::Executor.new((ENV["N"] || 2).to_i)
-
-
##
-
# Filter object for backtraces.
-
-
1
mc.send :attr_accessor, :backtrace_filter
-
-
##
-
# Reporter object to be used for all runs.
-
#
-
# NOTE: This accessor is only available during setup, not during runs.
-
-
1
mc.send :attr_accessor, :reporter
-
-
##
-
# Names of known extension plugins.
-
-
1
mc.send :attr_accessor, :extensions
-
-
##
-
# Registers Minitest to run at process exit
-
-
1
def self.autorun
-
at_exit {
-
next if $! and not ($!.kind_of? SystemExit and $!.success?)
-
-
exit_code = nil
-
-
at_exit {
-
@@after_run.reverse_each(&:call)
-
exit exit_code || false
-
}
-
-
exit_code = Minitest.run ARGV
-
} unless @@installed_at_exit
-
@@installed_at_exit = true
-
end
-
-
##
-
# A simple hook allowing you to run a block of code after everything
-
# is done running. Eg:
-
#
-
# Minitest.after_run { p $debugging_info }
-
-
1
def self.after_run &block
-
@@after_run << block
-
end
-
-
1
def self.init_plugins options # :nodoc:
-
self.extensions.each do |name|
-
msg = "plugin_#{name}_init"
-
send msg, options if self.respond_to? msg
-
end
-
end
-
-
1
def self.load_plugins # :nodoc:
-
return unless self.extensions.empty?
-
-
seen = {}
-
-
require "rubygems" unless defined? Gem
-
-
Gem.find_files("minitest/*_plugin.rb").each do |plugin_path|
-
name = File.basename plugin_path, "_plugin.rb"
-
-
next if seen[name]
-
seen[name] = true
-
-
require plugin_path
-
self.extensions << name
-
end
-
end
-
-
##
-
# This is the top-level run method. Everything starts from here. It
-
# tells each Runnable sub-class to run, and each of those are
-
# responsible for doing whatever they do.
-
#
-
# The overall structure of a run looks like this:
-
#
-
# Minitest.autorun
-
# Minitest.run(args)
-
# Minitest.__run(reporter, options)
-
# Runnable.runnables.each
-
# runnable.run(reporter, options)
-
# self.runnable_methods.each
-
# self.run_one_method(self, runnable_method, reporter)
-
# Minitest.run_one_method(klass, runnable_method)
-
# klass.new(runnable_method).run
-
-
1
def self.run args = []
-
self.load_plugins
-
-
options = process_args args
-
-
reporter = CompositeReporter.new
-
reporter << SummaryReporter.new(options[:io], options)
-
reporter << ProgressReporter.new(options[:io], options)
-
-
self.reporter = reporter # this makes it available to plugins
-
self.init_plugins options
-
self.reporter = nil # runnables shouldn't depend on the reporter, ever
-
-
self.parallel_executor.start if parallel_executor.respond_to?(:start)
-
reporter.start
-
begin
-
__run reporter, options
-
rescue Interrupt
-
warn "Interrupted. Exiting..."
-
end
-
self.parallel_executor.shutdown
-
reporter.report
-
-
reporter.passed?
-
end
-
-
##
-
# Internal run method. Responsible for telling all Runnable
-
# sub-classes to run.
-
#
-
# NOTE: this method is redefined in parallel_each.rb, which is
-
# loaded if a Runnable calls parallelize_me!.
-
-
1
def self.__run reporter, options
-
suites = Runnable.runnables.shuffle
-
parallel, serial = suites.partition { |s| s.test_order == :parallel }
-
-
# If we run the parallel tests before the serial tests, the parallel tests
-
# could run in parallel with the serial tests. This would be bad because
-
# the serial tests won't lock around Reporter#record. Run the serial tests
-
# first, so that after they complete, the parallel tests will lock when
-
# recording results.
-
serial.map { |suite| suite.run reporter, options } +
-
parallel.map { |suite| suite.run reporter, options }
-
end
-
-
1
def self.process_args args = [] # :nodoc:
-
options = {
-
:io => $stdout,
-
}
-
orig_args = args.dup
-
-
OptionParser.new do |opts|
-
opts.banner = "minitest options:"
-
opts.version = Minitest::VERSION
-
-
opts.on "-h", "--help", "Display this help." do
-
puts opts
-
exit
-
end
-
-
desc = "Sets random seed. Also via env. Eg: SEED=n rake"
-
opts.on "-s", "--seed SEED", Integer, desc do |m|
-
options[:seed] = m.to_i
-
end
-
-
opts.on "-v", "--verbose", "Verbose. Show progress processing files." do
-
options[:verbose] = true
-
end
-
-
opts.on "-n", "--name PATTERN", "Filter run on /regexp/ or string." do |a|
-
options[:filter] = a
-
end
-
-
unless extensions.empty?
-
opts.separator ""
-
opts.separator "Known extensions: #{extensions.join(", ")}"
-
-
extensions.each do |meth|
-
msg = "plugin_#{meth}_options"
-
send msg, opts, options if self.respond_to?(msg)
-
end
-
end
-
-
begin
-
opts.parse! args
-
rescue OptionParser::InvalidOption => e
-
puts
-
puts e
-
puts
-
puts opts
-
exit 1
-
end
-
-
orig_args -= args
-
end
-
-
unless options[:seed] then
-
srand
-
options[:seed] = (ENV["SEED"] || srand).to_i % 0xFFFF
-
orig_args << "--seed" << options[:seed].to_s
-
end
-
-
srand options[:seed]
-
-
options[:args] = orig_args.map { |s|
-
s =~ /[\s|&<>$()]/ ? s.inspect : s
-
}.join " "
-
-
options
-
end
-
-
1
def self.filter_backtrace bt # :nodoc:
-
backtrace_filter.filter bt
-
end
-
-
##
-
# Represents anything "runnable", like Test, Spec, Benchmark, or
-
# whatever you can dream up.
-
#
-
# Subclasses of this are automatically registered and available in
-
# Runnable.runnables.
-
-
1
class Runnable
-
##
-
# Number of assertions executed in this run.
-
-
1
attr_accessor :assertions
-
-
##
-
# An assertion raised during the run, if any.
-
-
1
attr_accessor :failures
-
-
##
-
# Name of the run.
-
-
1
def name
-
@NAME
-
end
-
-
##
-
# Set the name of the run.
-
-
1
def name= o
-
@NAME = o
-
end
-
-
1
def self.inherited klass # :nodoc:
-
7
self.runnables << klass
-
7
super
-
end
-
-
##
-
# Returns all instance methods matching the pattern +re+.
-
-
1
def self.methods_matching re
-
public_instance_methods(true).grep(re).map(&:to_s)
-
end
-
-
1
def self.reset # :nodoc:
-
1
@@runnables = []
-
end
-
-
1
reset
-
-
##
-
# Responsible for running all runnable methods in a given class,
-
# each in its own instance. Each instance is passed to the
-
# reporter to record.
-
-
1
def self.run reporter, options = {}
-
filter = options[:filter] || "/./"
-
filter = Regexp.new $1 if filter =~ %r%/(.*)/%
-
-
filtered_methods = self.runnable_methods.find_all { |m|
-
filter === m || filter === "#{self}##{m}"
-
}
-
-
return if filtered_methods.empty?
-
-
with_info_handler reporter do
-
filtered_methods.each do |method_name|
-
run_one_method self, method_name, reporter
-
end
-
end
-
end
-
-
##
-
# Runs a single method and has the reporter record the result.
-
# This was considered internal API but is factored out of run so
-
# that subclasses can specialize the running of an individual
-
# test. See Minitest::ParallelTest::ClassMethods for an example.
-
-
1
def self.run_one_method klass, method_name, reporter
-
reporter.record Minitest.run_one_method(klass, method_name)
-
end
-
-
1
def self.with_info_handler reporter, &block # :nodoc:
-
handler = lambda do
-
unless reporter.passed? then
-
warn "Current results:"
-
warn ""
-
warn reporter.reporters.first
-
warn ""
-
end
-
end
-
-
on_signal "INFO", handler, &block
-
end
-
-
1
SIGNALS = Signal.list # :nodoc:
-
-
1
def self.on_signal name, action # :nodoc:
-
supported = SIGNALS[name]
-
-
old_trap = trap name do
-
old_trap.call if old_trap.respond_to? :call
-
action.call
-
end if supported
-
-
yield
-
ensure
-
trap name, old_trap if supported
-
end
-
-
##
-
# Each subclass of Runnable is responsible for overriding this
-
# method to return all runnable methods. See #methods_matching.
-
-
1
def self.runnable_methods
-
raise NotImplementedError, "subclass responsibility"
-
end
-
-
##
-
# Returns all subclasses of Runnable.
-
-
1
def self.runnables
-
7
@@runnables
-
end
-
-
1
def marshal_dump # :nodoc:
-
[self.name, self.failures, self.assertions]
-
end
-
-
1
def marshal_load ary # :nodoc:
-
self.name, self.failures, self.assertions = ary
-
end
-
-
1
def failure # :nodoc:
-
self.failures.first
-
end
-
-
1
def initialize name # :nodoc:
-
self.name = name
-
self.failures = []
-
self.assertions = 0
-
end
-
-
##
-
# Runs a single method. Needs to return self.
-
-
1
def run
-
raise NotImplementedError, "subclass responsibility"
-
end
-
-
##
-
# Did this run pass?
-
#
-
# Note: skipped runs are not considered passing, but they don't
-
# cause the process to exit non-zero.
-
-
1
def passed?
-
raise NotImplementedError, "subclass responsibility"
-
end
-
-
##
-
# Returns a single character string to print based on the result
-
# of the run. Eg ".", "F", or "E".
-
-
1
def result_code
-
raise NotImplementedError, "subclass responsibility"
-
end
-
-
##
-
# Was this run skipped? See #passed? for more information.
-
-
1
def skipped?
-
raise NotImplementedError, "subclass responsibility"
-
end
-
end
-
-
##
-
# Defines the API for Reporters. Subclass this and override whatever
-
# you want. Go nuts.
-
-
1
class AbstractReporter
-
1
include Mutex_m
-
-
##
-
# Starts reporting on the run.
-
-
1
def start
-
end
-
-
##
-
# Record a result and output the Runnable#result_code. Stores the
-
# result of the run if the run did not pass.
-
-
1
def record result
-
end
-
-
##
-
# Outputs the summary of the run.
-
-
1
def report
-
end
-
-
##
-
# Did this run pass?
-
-
1
def passed?
-
true
-
end
-
end
-
-
1
class Reporter < AbstractReporter # :nodoc:
-
##
-
# The IO used to report.
-
-
1
attr_accessor :io
-
-
##
-
# Command-line options for this run.
-
-
1
attr_accessor :options
-
-
1
def initialize io = $stdout, options = {} # :nodoc:
-
super()
-
self.io = io
-
self.options = options
-
end
-
end
-
-
##
-
# A very simple reporter that prints the "dots" during the run.
-
#
-
# This is added to the top-level CompositeReporter at the start of
-
# the run. If you want to change the output of minitest via a
-
# plugin, pull this out of the composite and replace it with your
-
# own.
-
-
1
class ProgressReporter < Reporter
-
1
def record result # :nodoc:
-
io.print "%s#%s = %.2f s = " % [result.class, result.name, result.time] if
-
options[:verbose]
-
io.print result.result_code
-
io.puts if options[:verbose]
-
end
-
end
-
-
##
-
# A reporter that gathers statistics about a test run. Does not do
-
# any IO because meant to be used as a parent class for a reporter
-
# that does.
-
#
-
# If you want to create an entirely different type of output (eg,
-
# CI, HTML, etc), this is the place to start.
-
-
1
class StatisticsReporter < Reporter
-
# :stopdoc:
-
1
attr_accessor :assertions
-
1
attr_accessor :count
-
1
attr_accessor :results
-
1
attr_accessor :start_time
-
1
attr_accessor :total_time
-
1
attr_accessor :failures
-
1
attr_accessor :errors
-
1
attr_accessor :skips
-
# :startdoc:
-
-
1
def initialize io = $stdout, options = {} # :nodoc:
-
super
-
-
self.assertions = 0
-
self.count = 0
-
self.results = []
-
self.start_time = nil
-
self.total_time = nil
-
self.failures = nil
-
self.errors = nil
-
self.skips = nil
-
end
-
-
1
def passed? # :nodoc:
-
results.all?(&:skipped?)
-
end
-
-
1
def start # :nodoc:
-
self.start_time = Minitest.clock_time
-
end
-
-
1
def record result # :nodoc:
-
self.count += 1
-
self.assertions += result.assertions
-
-
results << result if not result.passed? or result.skipped?
-
end
-
-
1
def report # :nodoc:
-
aggregate = results.group_by { |r| r.failure.class }
-
aggregate.default = [] # dumb. group_by should provide this
-
-
self.total_time = Minitest.clock_time - start_time
-
self.failures = aggregate[Assertion].size
-
self.errors = aggregate[UnexpectedError].size
-
self.skips = aggregate[Skip].size
-
end
-
end
-
-
##
-
# A reporter that prints the header, summary, and failure details at
-
# the end of the run.
-
#
-
# This is added to the top-level CompositeReporter at the start of
-
# the run. If you want to change the output of minitest via a
-
# plugin, pull this out of the composite and replace it with your
-
# own.
-
-
1
class SummaryReporter < StatisticsReporter
-
# :stopdoc:
-
1
attr_accessor :sync
-
1
attr_accessor :old_sync
-
# :startdoc:
-
-
1
def start # :nodoc:
-
super
-
-
io.puts "Run options: #{options[:args]}"
-
io.puts
-
io.puts "# Running:"
-
io.puts
-
-
self.sync = io.respond_to? :"sync=" # stupid emacs
-
self.old_sync, io.sync = io.sync, true if self.sync
-
end
-
-
1
def report # :nodoc:
-
super
-
-
io.sync = self.old_sync
-
-
io.puts unless options[:verbose] # finish the dots
-
io.puts
-
io.puts statistics
-
io.puts aggregated_results
-
io.puts summary
-
end
-
-
1
def statistics # :nodoc:
-
"Finished in %.6fs, %.4f runs/s, %.4f assertions/s." %
-
[total_time, count / total_time, assertions / total_time]
-
end
-
-
1
def aggregated_results # :nodoc:
-
filtered_results = results.dup
-
filtered_results.reject!(&:skipped?) unless options[:verbose]
-
-
s = filtered_results.each_with_index.map { |result, i|
-
"\n%3d) %s" % [i+1, result]
-
}.join("\n") + "\n"
-
-
s.force_encoding(io.external_encoding) if
-
ENCS and io.external_encoding and s.encoding != io.external_encoding
-
-
s
-
end
-
-
1
alias to_s aggregated_results
-
-
1
def summary # :nodoc:
-
extra = ""
-
-
extra = "\n\nYou have skipped tests. Run with --verbose for details." if
-
results.any?(&:skipped?) unless options[:verbose] or ENV["MT_NO_SKIP_MSG"]
-
-
"%d runs, %d assertions, %d failures, %d errors, %d skips%s" %
-
[count, assertions, failures, errors, skips, extra]
-
end
-
end
-
-
##
-
# Dispatch to multiple reporters as one.
-
-
1
class CompositeReporter < AbstractReporter
-
##
-
# The list of reporters to dispatch to.
-
-
1
attr_accessor :reporters
-
-
1
def initialize *reporters # :nodoc:
-
super()
-
self.reporters = reporters
-
end
-
-
##
-
# Add another reporter to the mix.
-
-
1
def << reporter
-
self.reporters << reporter
-
end
-
-
1
def passed? # :nodoc:
-
self.reporters.all?(&:passed?)
-
end
-
-
1
def start # :nodoc:
-
self.reporters.each(&:start)
-
end
-
-
1
def record result # :nodoc:
-
self.reporters.each do |reporter|
-
reporter.record result
-
end
-
end
-
-
1
def report # :nodoc:
-
self.reporters.each(&:report)
-
end
-
end
-
-
##
-
# Represents run failures.
-
-
1
class Assertion < Exception
-
1
def error # :nodoc:
-
self
-
end
-
-
##
-
# Where was this run before an assertion was raised?
-
-
1
def location
-
last_before_assertion = ""
-
self.backtrace.reverse_each do |s|
-
break if s =~ /in .(assert|refute|flunk|pass|fail|raise|must|wont)/
-
last_before_assertion = s
-
end
-
last_before_assertion.sub(/:in .*$/, "")
-
end
-
-
1
def result_code # :nodoc:
-
result_label[0, 1]
-
end
-
-
1
def result_label # :nodoc:
-
"Failure"
-
end
-
end
-
-
##
-
# Assertion raised when skipping a run.
-
-
1
class Skip < Assertion
-
1
def result_label # :nodoc:
-
"Skipped"
-
end
-
end
-
-
##
-
# Assertion wrapping an unexpected error that was raised during a run.
-
-
1
class UnexpectedError < Assertion
-
1
attr_accessor :exception # :nodoc:
-
-
1
def initialize exception # :nodoc:
-
super "Unexpected exception"
-
self.exception = exception
-
end
-
-
1
def backtrace # :nodoc:
-
self.exception.backtrace
-
end
-
-
1
def error # :nodoc:
-
self.exception
-
end
-
-
1
def message # :nodoc:
-
bt = Minitest.filter_backtrace(self.backtrace).join "\n "
-
"#{self.exception.class}: #{self.exception.message}\n #{bt}"
-
end
-
-
1
def result_label # :nodoc:
-
"Error"
-
end
-
end
-
-
##
-
# Provides a simple set of guards that you can use in your tests
-
# to skip execution if it is not applicable. These methods are
-
# mixed into Test as both instance and class methods so you
-
# can use them inside or outside of the test methods.
-
#
-
# def test_something_for_mri
-
# skip "bug 1234" if jruby?
-
# # ...
-
# end
-
#
-
# if windows? then
-
# # ... lots of test methods ...
-
# end
-
-
1
module Guard
-
-
##
-
# Is this running on jruby?
-
-
1
def jruby? platform = RUBY_PLATFORM
-
"java" == platform
-
end
-
-
##
-
# Is this running on maglev?
-
-
1
def maglev? platform = defined?(RUBY_ENGINE) && RUBY_ENGINE
-
"maglev" == platform
-
end
-
-
##
-
# Is this running on mri?
-
-
1
def mri? platform = RUBY_DESCRIPTION
-
/^ruby/ =~ platform
-
end
-
-
##
-
# Is this running on rubinius?
-
-
1
def rubinius? platform = defined?(RUBY_ENGINE) && RUBY_ENGINE
-
"rbx" == platform
-
end
-
-
##
-
# Is this running on windows?
-
-
1
def windows? platform = RUBY_PLATFORM
-
/mswin|mingw/ =~ platform
-
end
-
end
-
-
1
class BacktraceFilter # :nodoc:
-
1
def filter bt
-
return ["No backtrace"] unless bt
-
-
return bt.dup if $DEBUG
-
-
new_bt = bt.take_while { |line| line !~ /lib\/minitest/ }
-
new_bt = bt.select { |line| line !~ /lib\/minitest/ } if new_bt.empty?
-
new_bt = bt.dup if new_bt.empty?
-
-
new_bt
-
end
-
end
-
-
1
self.backtrace_filter = BacktraceFilter.new
-
-
1
def self.run_one_method klass, method_name # :nodoc:
-
result = klass.new(method_name).run
-
raise "#{klass}#run _must_ return self" unless klass === result
-
result
-
end
-
-
1
if defined? Process::CLOCK_MONOTONIC
-
1
def self.clock_time
-
Process.clock_gettime Process::CLOCK_MONOTONIC
-
end
-
else
-
def self.clock_time
-
Time.now
-
end
-
end
-
end
-
-
1
require "minitest/test"
-
1
require "rbconfig"
-
1
require "tempfile"
-
1
require "stringio"
-
-
1
module Minitest
-
##
-
# Minitest Assertions. All assertion methods accept a +msg+ which is
-
# printed if the assertion fails.
-
#
-
# Protocol: Nearly everything here boils up to +assert+, which
-
# expects to be able to increment an instance accessor named
-
# +assertions+. This is not provided by Assertions and must be
-
# provided by the thing including Assertions. See Minitest::Runnable
-
# for an example.
-
-
1
module Assertions
-
1
UNDEFINED = Object.new # :nodoc:
-
-
1
def UNDEFINED.inspect # :nodoc:
-
"UNDEFINED" # again with the rdoc bugs... :(
-
end
-
-
##
-
# Returns the diff command to use in #diff. Tries to intelligently
-
# figure out what diff to use.
-
-
1
def self.diff
-
@diff = if (RbConfig::CONFIG["host_os"] =~ /mswin|mingw/ &&
-
system("diff.exe", __FILE__, __FILE__)) then
-
"diff.exe -u"
-
elsif Minitest::Test.maglev? then
-
"diff -u"
-
elsif system("gdiff", __FILE__, __FILE__)
-
"gdiff -u" # solaris and kin suck
-
elsif system("diff", __FILE__, __FILE__)
-
"diff -u"
-
else
-
nil
-
end unless defined? @diff
-
-
@diff
-
end
-
-
##
-
# Set the diff command to use in #diff.
-
-
1
def self.diff= o
-
@diff = o
-
end
-
-
##
-
# Returns a diff between +exp+ and +act+. If there is no known
-
# diff command or if it doesn't make sense to diff the output
-
# (single line, short output), then it simply returns a basic
-
# comparison between the two.
-
-
1
def diff exp, act
-
expect = mu_pp_for_diff exp
-
butwas = mu_pp_for_diff act
-
result = nil
-
-
need_to_diff =
-
(expect.include?("\n") ||
-
butwas.include?("\n") ||
-
expect.size > 30 ||
-
butwas.size > 30 ||
-
expect == butwas) &&
-
Minitest::Assertions.diff
-
-
return "Expected: #{mu_pp exp}\n Actual: #{mu_pp act}" unless
-
need_to_diff
-
-
Tempfile.open("expect") do |a|
-
a.puts expect
-
a.flush
-
-
Tempfile.open("butwas") do |b|
-
b.puts butwas
-
b.flush
-
-
result = `#{Minitest::Assertions.diff} #{a.path} #{b.path}`
-
result.sub!(/^\-\-\- .+/, "--- expected")
-
result.sub!(/^\+\+\+ .+/, "+++ actual")
-
-
if result.empty? then
-
klass = exp.class
-
result = [
-
"No visible difference in the #{klass}#inspect output.\n",
-
"You should look at the implementation of #== on ",
-
"#{klass} or its members.\n",
-
expect,
-
].join
-
end
-
end
-
end
-
-
result
-
end
-
-
##
-
# This returns a human-readable version of +obj+. By default
-
# #inspect is called. You can override this to use #pretty_print
-
# if you want.
-
-
1
def mu_pp obj
-
s = obj.inspect
-
s = s.encode Encoding.default_external if defined? Encoding
-
s
-
end
-
-
##
-
# This returns a diff-able human-readable version of +obj+. This
-
# differs from the regular mu_pp because it expands escaped
-
# newlines and makes hex-values generic (like object_ids). This
-
# uses mu_pp to do the first pass and then cleans it up.
-
-
1
def mu_pp_for_diff obj
-
mu_pp(obj).gsub(/\\n/, "\n").gsub(/:0x[a-fA-F0-9]{4,}/m, ":0xXXXXXX")
-
end
-
-
##
-
# Fails unless +test+ is truthy.
-
-
1
def assert test, msg = nil
-
self.assertions += 1
-
unless test then
-
msg ||= "Failed assertion, no message given."
-
msg = msg.call if Proc === msg
-
raise Minitest::Assertion, msg
-
end
-
true
-
end
-
-
1
def _synchronize # :nodoc:
-
yield
-
end
-
-
##
-
# Fails unless +obj+ is empty.
-
-
1
def assert_empty obj, msg = nil
-
msg = message(msg) { "Expected #{mu_pp(obj)} to be empty" }
-
assert_respond_to obj, :empty?
-
assert obj.empty?, msg
-
end
-
-
1
E = "" # :nodoc:
-
-
##
-
# Fails unless <tt>exp == act</tt> printing the difference between
-
# the two, if possible.
-
#
-
# If there is no visible difference but the assertion fails, you
-
# should suspect that your #== is buggy, or your inspect output is
-
# missing crucial details.
-
#
-
# For floats use assert_in_delta.
-
#
-
# See also: Minitest::Assertions.diff
-
-
1
def assert_equal exp, act, msg = nil
-
msg = message(msg, E) { diff exp, act }
-
assert exp == act, msg
-
end
-
-
##
-
# For comparing Floats. Fails unless +exp+ and +act+ are within +delta+
-
# of each other.
-
#
-
# assert_in_delta Math::PI, (22.0 / 7.0), 0.01
-
-
1
def assert_in_delta exp, act, delta = 0.001, msg = nil
-
n = (exp - act).abs
-
msg = message(msg) {
-
"Expected |#{exp} - #{act}| (#{n}) to be <= #{delta}"
-
}
-
assert delta >= n, msg
-
end
-
-
##
-
# For comparing Floats. Fails unless +exp+ and +act+ have a relative
-
# error less than +epsilon+.
-
-
1
def assert_in_epsilon a, b, epsilon = 0.001, msg = nil
-
assert_in_delta a, b, [a.abs, b.abs].min * epsilon, msg
-
end
-
-
##
-
# Fails unless +collection+ includes +obj+.
-
-
1
def assert_includes collection, obj, msg = nil
-
msg = message(msg) {
-
"Expected #{mu_pp(collection)} to include #{mu_pp(obj)}"
-
}
-
assert_respond_to collection, :include?
-
assert collection.include?(obj), msg
-
end
-
-
##
-
# Fails unless +obj+ is an instance of +cls+.
-
-
1
def assert_instance_of cls, obj, msg = nil
-
msg = message(msg) {
-
"Expected #{mu_pp(obj)} to be an instance of #{cls}, not #{obj.class}"
-
}
-
-
assert obj.instance_of?(cls), msg
-
end
-
-
##
-
# Fails unless +obj+ is a kind of +cls+.
-
-
1
def assert_kind_of cls, obj, msg = nil
-
msg = message(msg) {
-
"Expected #{mu_pp(obj)} to be a kind of #{cls}, not #{obj.class}" }
-
-
assert obj.kind_of?(cls), msg
-
end
-
-
##
-
# Fails unless +matcher+ <tt>=~</tt> +obj+.
-
-
1
def assert_match matcher, obj, msg = nil
-
msg = message(msg) { "Expected #{mu_pp matcher} to match #{mu_pp obj}" }
-
assert_respond_to matcher, :"=~"
-
matcher = Regexp.new Regexp.escape matcher if String === matcher
-
assert matcher =~ obj, msg
-
end
-
-
##
-
# Fails unless +obj+ is nil
-
-
1
def assert_nil obj, msg = nil
-
msg = message(msg) { "Expected #{mu_pp(obj)} to be nil" }
-
assert obj.nil?, msg
-
end
-
-
##
-
# For testing with binary operators. Eg:
-
#
-
# assert_operator 5, :<=, 4
-
-
1
def assert_operator o1, op, o2 = UNDEFINED, msg = nil
-
return assert_predicate o1, op, msg if UNDEFINED == o2
-
msg = message(msg) { "Expected #{mu_pp(o1)} to be #{op} #{mu_pp(o2)}" }
-
assert o1.__send__(op, o2), msg
-
end
-
-
##
-
# Fails if stdout or stderr do not output the expected results.
-
# Pass in nil if you don't care about that streams output. Pass in
-
# "" if you require it to be silent. Pass in a regexp if you want
-
# to pattern match.
-
#
-
# NOTE: this uses #capture_io, not #capture_subprocess_io.
-
#
-
# See also: #assert_silent
-
-
1
def assert_output stdout = nil, stderr = nil
-
out, err = capture_io do
-
yield
-
end
-
-
err_msg = Regexp === stderr ? :assert_match : :assert_equal if stderr
-
out_msg = Regexp === stdout ? :assert_match : :assert_equal if stdout
-
-
y = send err_msg, stderr, err, "In stderr" if err_msg
-
x = send out_msg, stdout, out, "In stdout" if out_msg
-
-
(!stdout || x) && (!stderr || y)
-
end
-
-
##
-
# For testing with predicates. Eg:
-
#
-
# assert_predicate str, :empty?
-
#
-
# This is really meant for specs and is front-ended by assert_operator:
-
#
-
# str.must_be :empty?
-
-
1
def assert_predicate o1, op, msg = nil
-
msg = message(msg) { "Expected #{mu_pp(o1)} to be #{op}" }
-
assert o1.__send__(op), msg
-
end
-
-
##
-
# Fails unless the block raises one of +exp+. Returns the
-
# exception matched so you can check the message, attributes, etc.
-
#
-
# +exp+ takes an optional message on the end to help explain
-
# failures and defaults to StandardError if no exception class is
-
# passed.
-
-
1
def assert_raises *exp
-
msg = "#{exp.pop}.\n" if String === exp.last
-
exp << StandardError if exp.empty?
-
-
begin
-
yield
-
rescue *exp => e
-
pass # count assertion
-
return e
-
rescue Minitest::Skip
-
# don't count assertion
-
raise
-
rescue SignalException, SystemExit
-
raise
-
rescue Exception => e
-
flunk proc {
-
exception_details(e, "#{msg}#{mu_pp(exp)} exception expected, not")
-
}
-
end
-
-
exp = exp.first if exp.size == 1
-
-
flunk "#{msg}#{mu_pp(exp)} expected but nothing was raised."
-
end
-
-
##
-
# Fails unless +obj+ responds to +meth+.
-
-
1
def assert_respond_to obj, meth, msg = nil
-
msg = message(msg) {
-
"Expected #{mu_pp(obj)} (#{obj.class}) to respond to ##{meth}"
-
}
-
assert obj.respond_to?(meth), msg
-
end
-
-
##
-
# Fails unless +exp+ and +act+ are #equal?
-
-
1
def assert_same exp, act, msg = nil
-
msg = message(msg) {
-
data = [mu_pp(act), act.object_id, mu_pp(exp), exp.object_id]
-
"Expected %s (oid=%d) to be the same as %s (oid=%d)" % data
-
}
-
assert exp.equal?(act), msg
-
end
-
-
##
-
# +send_ary+ is a receiver, message and arguments.
-
#
-
# Fails unless the call returns a true value
-
-
1
def assert_send send_ary, m = nil
-
recv, msg, *args = send_ary
-
m = message(m) {
-
"Expected #{mu_pp(recv)}.#{msg}(*#{mu_pp(args)}) to return true" }
-
assert recv.__send__(msg, *args), m
-
end
-
-
##
-
# Fails if the block outputs anything to stderr or stdout.
-
#
-
# See also: #assert_output
-
-
1
def assert_silent
-
assert_output "", "" do
-
yield
-
end
-
end
-
-
##
-
# Fails unless the block throws +sym+
-
-
1
def assert_throws sym, msg = nil
-
default = "Expected #{mu_pp(sym)} to have been thrown"
-
caught = true
-
catch(sym) do
-
begin
-
yield
-
rescue ThreadError => e # wtf?!? 1.8 + threads == suck
-
default += ", not \:#{e.message[/uncaught throw \`(\w+?)\'/, 1]}"
-
rescue ArgumentError => e # 1.9 exception
-
default += ", not #{e.message.split(/ /).last}"
-
rescue NameError => e # 1.8 exception
-
default += ", not #{e.name.inspect}"
-
end
-
caught = false
-
end
-
-
assert caught, message(msg) { default }
-
end
-
-
##
-
# Captures $stdout and $stderr into strings:
-
#
-
# out, err = capture_io do
-
# puts "Some info"
-
# warn "You did a bad thing"
-
# end
-
#
-
# assert_match %r%info%, out
-
# assert_match %r%bad%, err
-
#
-
# NOTE: For efficiency, this method uses StringIO and does not
-
# capture IO for subprocesses. Use #capture_subprocess_io for
-
# that.
-
-
1
def capture_io
-
_synchronize do
-
begin
-
captured_stdout, captured_stderr = StringIO.new, StringIO.new
-
-
orig_stdout, orig_stderr = $stdout, $stderr
-
$stdout, $stderr = captured_stdout, captured_stderr
-
-
yield
-
-
return captured_stdout.string, captured_stderr.string
-
ensure
-
$stdout = orig_stdout
-
$stderr = orig_stderr
-
end
-
end
-
end
-
-
##
-
# Captures $stdout and $stderr into strings, using Tempfile to
-
# ensure that subprocess IO is captured as well.
-
#
-
# out, err = capture_subprocess_io do
-
# system "echo Some info"
-
# system "echo You did a bad thing 1>&2"
-
# end
-
#
-
# assert_match %r%info%, out
-
# assert_match %r%bad%, err
-
#
-
# NOTE: This method is approximately 10x slower than #capture_io so
-
# only use it when you need to test the output of a subprocess.
-
-
1
def capture_subprocess_io
-
_synchronize do
-
begin
-
require "tempfile"
-
-
captured_stdout, captured_stderr = Tempfile.new("out"), Tempfile.new("err")
-
-
orig_stdout, orig_stderr = $stdout.dup, $stderr.dup
-
$stdout.reopen captured_stdout
-
$stderr.reopen captured_stderr
-
-
yield
-
-
$stdout.rewind
-
$stderr.rewind
-
-
return captured_stdout.read, captured_stderr.read
-
ensure
-
captured_stdout.unlink
-
captured_stderr.unlink
-
$stdout.reopen orig_stdout
-
$stderr.reopen orig_stderr
-
end
-
end
-
end
-
-
##
-
# Returns details for exception +e+
-
-
1
def exception_details e, msg
-
[
-
"#{msg}",
-
"Class: <#{e.class}>",
-
"Message: <#{e.message.inspect}>",
-
"---Backtrace---",
-
"#{Minitest.filter_backtrace(e.backtrace).join("\n")}",
-
"---------------",
-
].join "\n"
-
end
-
-
##
-
# Fails with +msg+
-
-
1
def flunk msg = nil
-
msg ||= "Epic Fail!"
-
assert false, msg
-
end
-
-
##
-
# Returns a proc that will output +msg+ along with the default message.
-
-
1
def message msg = nil, ending = nil, &default
-
proc {
-
msg = msg.call.chomp(".") if Proc === msg
-
custom_message = "#{msg}.\n" unless msg.nil? or msg.to_s.empty?
-
"#{custom_message}#{default.call}#{ending || "."}"
-
}
-
end
-
-
##
-
# used for counting assertions
-
-
1
def pass _msg = nil
-
assert true
-
end
-
-
##
-
# Fails if +test+ is truthy.
-
-
1
def refute test, msg = nil
-
msg ||= "Failed refutation, no message given"
-
not assert !test, msg
-
end
-
-
##
-
# Fails if +obj+ is empty.
-
-
1
def refute_empty obj, msg = nil
-
msg = message(msg) { "Expected #{mu_pp(obj)} to not be empty" }
-
assert_respond_to obj, :empty?
-
refute obj.empty?, msg
-
end
-
-
##
-
# Fails if <tt>exp == act</tt>.
-
#
-
# For floats use refute_in_delta.
-
-
1
def refute_equal exp, act, msg = nil
-
msg = message(msg) {
-
"Expected #{mu_pp(act)} to not be equal to #{mu_pp(exp)}"
-
}
-
refute exp == act, msg
-
end
-
-
##
-
# For comparing Floats. Fails if +exp+ is within +delta+ of +act+.
-
#
-
# refute_in_delta Math::PI, (22.0 / 7.0)
-
-
1
def refute_in_delta exp, act, delta = 0.001, msg = nil
-
n = (exp - act).abs
-
msg = message(msg) {
-
"Expected |#{exp} - #{act}| (#{n}) to not be <= #{delta}"
-
}
-
refute delta >= n, msg
-
end
-
-
##
-
# For comparing Floats. Fails if +exp+ and +act+ have a relative error
-
# less than +epsilon+.
-
-
1
def refute_in_epsilon a, b, epsilon = 0.001, msg = nil
-
refute_in_delta a, b, a * epsilon, msg
-
end
-
-
##
-
# Fails if +collection+ includes +obj+.
-
-
1
def refute_includes collection, obj, msg = nil
-
msg = message(msg) {
-
"Expected #{mu_pp(collection)} to not include #{mu_pp(obj)}"
-
}
-
assert_respond_to collection, :include?
-
refute collection.include?(obj), msg
-
end
-
-
##
-
# Fails if +obj+ is an instance of +cls+.
-
-
1
def refute_instance_of cls, obj, msg = nil
-
msg = message(msg) {
-
"Expected #{mu_pp(obj)} to not be an instance of #{cls}"
-
}
-
refute obj.instance_of?(cls), msg
-
end
-
-
##
-
# Fails if +obj+ is a kind of +cls+.
-
-
1
def refute_kind_of cls, obj, msg = nil
-
msg = message(msg) { "Expected #{mu_pp(obj)} to not be a kind of #{cls}" }
-
refute obj.kind_of?(cls), msg
-
end
-
-
##
-
# Fails if +matcher+ <tt>=~</tt> +obj+.
-
-
1
def refute_match matcher, obj, msg = nil
-
msg = message(msg) { "Expected #{mu_pp matcher} to not match #{mu_pp obj}" }
-
assert_respond_to matcher, :"=~"
-
matcher = Regexp.new Regexp.escape matcher if String === matcher
-
refute matcher =~ obj, msg
-
end
-
-
##
-
# Fails if +obj+ is nil.
-
-
1
def refute_nil obj, msg = nil
-
msg = message(msg) { "Expected #{mu_pp(obj)} to not be nil" }
-
refute obj.nil?, msg
-
end
-
-
##
-
# Fails if +o1+ is not +op+ +o2+. Eg:
-
#
-
# refute_operator 1, :>, 2 #=> pass
-
# refute_operator 1, :<, 2 #=> fail
-
-
1
def refute_operator o1, op, o2 = UNDEFINED, msg = nil
-
return refute_predicate o1, op, msg if UNDEFINED == o2
-
msg = message(msg) { "Expected #{mu_pp(o1)} to not be #{op} #{mu_pp(o2)}" }
-
refute o1.__send__(op, o2), msg
-
end
-
-
##
-
# For testing with predicates.
-
#
-
# refute_predicate str, :empty?
-
#
-
# This is really meant for specs and is front-ended by refute_operator:
-
#
-
# str.wont_be :empty?
-
-
1
def refute_predicate o1, op, msg = nil
-
msg = message(msg) { "Expected #{mu_pp(o1)} to not be #{op}" }
-
refute o1.__send__(op), msg
-
end
-
-
##
-
# Fails if +obj+ responds to the message +meth+.
-
-
1
def refute_respond_to obj, meth, msg = nil
-
msg = message(msg) { "Expected #{mu_pp(obj)} to not respond to #{meth}" }
-
-
refute obj.respond_to?(meth), msg
-
end
-
-
##
-
# Fails if +exp+ is the same (by object identity) as +act+.
-
-
1
def refute_same exp, act, msg = nil
-
msg = message(msg) {
-
data = [mu_pp(act), act.object_id, mu_pp(exp), exp.object_id]
-
"Expected %s (oid=%d) to not be the same as %s (oid=%d)" % data
-
}
-
refute exp.equal?(act), msg
-
end
-
-
##
-
# Skips the current run. If run in verbose-mode, the skipped run
-
# gets listed at the end of the run but doesn't cause a failure
-
# exit code.
-
-
1
def skip msg = nil, bt = caller
-
msg ||= "Skipped, no message given"
-
@skip = true
-
raise Minitest::Skip, msg, bt
-
end
-
-
##
-
# Was this testcase skipped? Meant for #teardown.
-
-
1
def skipped?
-
defined?(@skip) and @skip
-
end
-
end
-
end
-
1
module Minitest
-
1
module Parallel
-
-
##
-
# The engine used to run multiple tests in parallel.
-
-
1
class Executor
-
-
##
-
# The size of the pool of workers.
-
-
1
attr_reader :size
-
-
##
-
# Create a parallel test executor of with +size+ workers.
-
-
1
def initialize size
-
1
@size = size
-
1
@queue = Queue.new
-
1
@pool = nil
-
end
-
-
##
-
# Start the executor
-
-
1
def start
-
@pool = size.times.map {
-
Thread.new(@queue) do |queue|
-
Thread.current.abort_on_exception = true
-
while (job = queue.pop)
-
klass, method, reporter = job
-
result = Minitest.run_one_method klass, method
-
reporter.synchronize { reporter.record result }
-
end
-
end
-
}
-
end
-
-
##
-
# Add a job to the queue
-
-
1
def << work; @queue << work; end
-
-
##
-
# Shuts down the pool of workers by signalling them to quit and
-
# waiting for them all to finish what they're currently working
-
# on.
-
-
1
def shutdown
-
size.times { @queue << nil }
-
@pool.each(&:join)
-
end
-
end
-
-
1
module Test
-
1
def _synchronize; Minitest::Test.io_lock.synchronize { yield }; end # :nodoc:
-
-
1
module ClassMethods # :nodoc:
-
1
def run_one_method klass, method_name, reporter
-
Minitest.parallel_executor << [klass, method_name, reporter]
-
end
-
-
1
def test_order
-
:parallel
-
end
-
end
-
end
-
end
-
end
-
1
require "minitest" unless defined? Minitest::Runnable
-
-
1
module Minitest
-
##
-
# Subclass Test to create your own tests. Typically you'll want a
-
# Test subclass per implementation class.
-
#
-
# See Minitest::Assertions
-
-
1
class Test < Runnable
-
1
require "minitest/assertions"
-
1
include Minitest::Assertions
-
-
1
PASSTHROUGH_EXCEPTIONS = [NoMemoryError, SignalException, # :nodoc:
-
Interrupt, SystemExit]
-
-
2
class << self; attr_accessor :io_lock; end # :nodoc:
-
1
self.io_lock = Mutex.new
-
-
##
-
# Call this at the top of your tests when you absolutely
-
# positively need to have ordered tests. In doing so, you're
-
# admitting that you suck and your tests are weak.
-
-
1
def self.i_suck_and_my_tests_are_order_dependent!
-
class << self
-
undef_method :test_order if method_defined? :test_order
-
define_method :test_order do :alpha end
-
end
-
end
-
-
##
-
# Make diffs for this Test use #pretty_inspect so that diff
-
# in assert_equal can have more details. NOTE: this is much slower
-
# than the regular inspect but much more usable for complex
-
# objects.
-
-
1
def self.make_my_diffs_pretty!
-
require "pp"
-
-
define_method :mu_pp do |o|
-
o.pretty_inspect
-
end
-
end
-
-
##
-
# Call this at the top of your tests when you want to run your
-
# tests in parallel. In doing so, you're admitting that you rule
-
# and your tests are awesome.
-
-
1
def self.parallelize_me!
-
include Minitest::Parallel::Test
-
extend Minitest::Parallel::Test::ClassMethods
-
end
-
-
##
-
# Returns all instance methods starting with "test_". Based on
-
# #test_order, the methods are either sorted, randomized
-
# (default), or run in parallel.
-
-
1
def self.runnable_methods
-
methods = methods_matching(/^test_/)
-
-
case self.test_order
-
when :random, :parallel then
-
max = methods.size
-
methods.sort.sort_by { rand max }
-
when :alpha, :sorted then
-
methods.sort
-
else
-
raise "Unknown test_order: #{self.test_order.inspect}"
-
end
-
end
-
-
##
-
# Defines the order to run tests (:random by default). Override
-
# this or use a convenience method to change it for your tests.
-
-
1
def self.test_order
-
:random
-
end
-
-
##
-
# The time it took to run this test.
-
-
1
attr_accessor :time
-
-
1
def marshal_dump # :nodoc:
-
super << self.time
-
end
-
-
1
def marshal_load ary # :nodoc:
-
self.time = ary.pop
-
super
-
end
-
-
1
TEARDOWN_METHODS = %w[ before_teardown teardown after_teardown ] # :nodoc:
-
-
##
-
# Runs a single test with setup/teardown hooks.
-
-
1
def run
-
with_info_handler do
-
time_it do
-
capture_exceptions do
-
before_setup; setup; after_setup
-
-
self.send self.name
-
end
-
-
TEARDOWN_METHODS.each do |hook|
-
capture_exceptions do
-
self.send hook
-
end
-
end
-
end
-
end
-
-
self # per contract
-
end
-
-
##
-
# Provides before/after hooks for setup and teardown. These are
-
# meant for library writers, NOT for regular test authors. See
-
# #before_setup for an example.
-
-
1
module LifecycleHooks
-
-
##
-
# Runs before every test, before setup. This hook is meant for
-
# libraries to extend minitest. It is not meant to be used by
-
# test developers.
-
#
-
# As a simplistic example:
-
#
-
# module MyMinitestPlugin
-
# def before_setup
-
# super
-
# # ... stuff to do before setup is run
-
# end
-
#
-
# def after_setup
-
# # ... stuff to do after setup is run
-
# super
-
# end
-
#
-
# def before_teardown
-
# super
-
# # ... stuff to do before teardown is run
-
# end
-
#
-
# def after_teardown
-
# # ... stuff to do after teardown is run
-
# super
-
# end
-
# end
-
#
-
# class MiniTest::Test
-
# include MyMinitestPlugin
-
# end
-
-
1
def before_setup; end
-
-
##
-
# Runs before every test. Use this to set up before each test
-
# run.
-
-
1
def setup; end
-
-
##
-
# Runs before every test, after setup. This hook is meant for
-
# libraries to extend minitest. It is not meant to be used by
-
# test developers.
-
#
-
# See #before_setup for an example.
-
-
1
def after_setup; end
-
-
##
-
# Runs after every test, before teardown. This hook is meant for
-
# libraries to extend minitest. It is not meant to be used by
-
# test developers.
-
#
-
# See #before_setup for an example.
-
-
1
def before_teardown; end
-
-
##
-
# Runs after every test. Use this to clean up after each test
-
# run.
-
-
1
def teardown; end
-
-
##
-
# Runs after every test, after teardown. This hook is meant for
-
# libraries to extend minitest. It is not meant to be used by
-
# test developers.
-
#
-
# See #before_setup for an example.
-
-
1
def after_teardown; end
-
end # LifecycleHooks
-
-
1
def capture_exceptions # :nodoc:
-
yield
-
rescue *PASSTHROUGH_EXCEPTIONS
-
raise
-
rescue Assertion => e
-
self.failures << e
-
rescue Exception => e
-
self.failures << UnexpectedError.new(e)
-
end
-
-
##
-
# Did this run error?
-
-
1
def error?
-
self.failures.any? { |f| UnexpectedError === f }
-
end
-
-
##
-
# The location identifier of this test.
-
-
1
def location
-
loc = " [#{self.failure.location}]" unless passed? or error?
-
"#{self.class}##{self.name}#{loc}"
-
end
-
-
##
-
# Did this run pass?
-
#
-
# Note: skipped runs are not considered passing, but they don't
-
# cause the process to exit non-zero.
-
-
1
def passed?
-
not self.failure
-
end
-
-
##
-
# Returns ".", "F", or "E" based on the result of the run.
-
-
1
def result_code
-
self.failure and self.failure.result_code or "."
-
end
-
-
##
-
# Was this run skipped?
-
-
1
def skipped?
-
self.failure and Skip === self.failure
-
end
-
-
1
def time_it # :nodoc:
-
t0 = Minitest.clock_time
-
-
yield
-
ensure
-
self.time = Minitest.clock_time - t0
-
end
-
-
1
def to_s # :nodoc:
-
return location if passed? and not skipped?
-
-
failures.map { |failure|
-
"#{failure.result_label}:\n#{self.location}:\n#{failure.message}\n"
-
}.join "\n"
-
end
-
-
1
def with_info_handler &block # :nodoc:
-
t0 = Minitest.clock_time
-
-
handler = lambda do
-
warn "\nCurrent: %s#%s %.2fs" % [self.class, self.name, Minitest.clock_time - t0]
-
end
-
-
self.class.on_signal "INFO", handler, &block
-
end
-
-
1
include LifecycleHooks
-
1
include Guard
-
1
extend Guard
-
end # Test
-
end
-
-
1
require "minitest/unit" unless defined?(MiniTest) # compatibility layer only
-
# :stopdoc:
-
-
1
unless defined?(Minitest) then
-
# all of this crap is just to avoid circular requires and is only
-
# needed if a user requires "minitest/unit" directly instead of
-
# "minitest/autorun", so we also warn
-
-
from = caller.reject { |s| s =~ /rubygems/ }.join("\n ")
-
warn "Warning: you should require 'minitest/autorun' instead."
-
warn %(Warning: or add 'gem "minitest"' before 'require "minitest/autorun"')
-
warn "From:\n #{from}"
-
-
module Minitest; end
-
MiniTest = Minitest # prevents minitest.rb from requiring back to us
-
require "minitest"
-
end
-
-
1
MiniTest = Minitest unless defined?(MiniTest)
-
-
1
module Minitest
-
1
class Unit
-
1
VERSION = Minitest::VERSION
-
1
class TestCase < Minitest::Test
-
1
def self.inherited klass # :nodoc:
-
from = caller.first
-
warn "MiniTest::Unit::TestCase is now Minitest::Test. From #{from}"
-
super
-
end
-
end
-
-
1
def self.autorun # :nodoc:
-
from = caller.first
-
warn "MiniTest::Unit.autorun is now Minitest.autorun. From #{from}"
-
Minitest.autorun
-
end
-
-
1
def self.after_tests(&b)
-
from = caller.first
-
warn "MiniTest::Unit.after_tests is now Minitest.after_run. From #{from}"
-
Minitest.after_run(&b)
-
end
-
end
-
end
-
-
# :startdoc:
-
1
module PublicActivity
-
# Happens when creating custom activities without either action or a key.
-
1
class NoKeyProvided < Exception; end
-
-
# Used to smartly transform value from metadata to data.
-
# Accepts Symbols, which it will send against context.
-
# Accepts Procs, which it will execute with controller and context.
-
# @since 0.4.0
-
1
def self.resolve_value(context, thing)
-
63
case thing
-
when Symbol
-
context.__send__(thing)
-
when Proc
-
thing.call(PublicActivity.get_controller, context)
-
else
-
63
thing
-
end
-
end
-
-
# Common methods shared across the gem.
-
1
module Common
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
include Trackable
-
1
class_attribute :activity_owner_global, :activity_recipient_global,
-
:activity_params_global, :activity_hooks, :activity_custom_fields_global
-
1
set_public_activity_class_defaults
-
end
-
-
# @!group Global options
-
-
# @!attribute activity_owner_global
-
# Global version of activity owner
-
# @see #activity_owner
-
# @return [Model]
-
-
# @!attribute activity_recipient_global
-
# Global version of activity recipient
-
# @see #activity_recipient
-
# @return [Model]
-
-
# @!attribute activity_params_global
-
# Global version of activity parameters
-
# @see #activity_params
-
# @return [Hash<Symbol, Object>]
-
-
# @!attribute activity_hooks
-
# @return [Hash<Symbol, Proc>]
-
# Hooks/functions that will be used to decide *if* the activity should get
-
# created.
-
#
-
# The supported keys are:
-
# * :create
-
# * :update
-
# * :destroy
-
-
# @!endgroup
-
-
# @!group Instance options
-
-
# Set or get parameters that will be passed to {Activity} when saving
-
#
-
# == Usage:
-
#
-
# @article.activity_params = {:article_title => @article.title}
-
# @article.save
-
#
-
# This way you can pass strings that should remain constant, even when model attributes
-
# change after creating this {Activity}.
-
# @return [Hash<Symbol, Object>]
-
1
attr_accessor :activity_params
-
1
@activity_params = {}
-
# Set or get owner object responsible for the {Activity}.
-
#
-
# == Usage:
-
#
-
# # where current_user is an object of logged in user
-
# @article.activity_owner = current_user
-
# # OR: take @article.author association
-
# @article.activity_owner = :author
-
# # OR: provide a Proc with custom code
-
# @article.activity_owner = proc {|controller, model| model.author }
-
# @article.save
-
# @article.activities.last.owner #=> Returns owner object
-
# @return [Model] Polymorphic model
-
# @see #activity_owner_global
-
1
attr_accessor :activity_owner
-
1
@activity_owner = nil
-
-
# Set or get recipient for activity.
-
#
-
# Association is polymorphic, thus allowing assignment of
-
# all types of models. This can be used for example in the case of sending
-
# private notifications for only a single user.
-
# @return (see #activity_owner)
-
1
attr_accessor :activity_recipient
-
1
@activity_recipient = nil
-
# Set or get custom i18n key passed to {Activity}, later used in {Renderable#text}
-
#
-
# == Usage:
-
#
-
# @article = Article.new
-
# @article.activity_key = "my.custom.article.key"
-
# @article.save
-
# @article.activities.last.key #=> "my.custom.article.key"
-
#
-
# @return [String]
-
1
attr_accessor :activity_key
-
1
@activity_key = nil
-
-
# Set or get custom fields for later processing
-
#
-
# @return [Hash]
-
1
attr_accessor :activity_custom_fields
-
1
@activity_custom_fields = {}
-
-
# @!visibility private
-
1
@@activity_hooks = {}
-
-
# @!endgroup
-
-
# Provides some global methods for every model class.
-
1
module ClassMethods
-
#
-
# @since 1.0.0
-
# @api private
-
1
def set_public_activity_class_defaults
-
2
self.activity_owner_global = nil
-
2
self.activity_recipient_global = nil
-
2
self.activity_params_global = {}
-
2
self.activity_hooks = {}
-
2
self.activity_custom_fields_global = {}
-
end
-
-
# Extracts a hook from the _:on_ option provided in
-
# {Tracked::ClassMethods#tracked}. Returns nil when no hook exists for
-
# given action
-
# {Common#get_hook}
-
#
-
# @see Tracked#get_hook
-
# @param key [String, Symbol] action to retrieve a hook for
-
# @return [Proc, nil] callable hook or nil
-
# @since 0.4.0
-
# @api private
-
1
def get_hook(key)
-
9
key = key.to_sym
-
9
if self.activity_hooks.has_key?(key) and self.activity_hooks[key].is_a? Proc
-
self.activity_hooks[key]
-
else
-
nil
-
end
-
end
-
end
-
#
-
# Returns true if PublicActivity is enabled
-
# globally and for this class.
-
# @return [Boolean]
-
# @api private
-
# @since 0.5.0
-
1
def public_activity_enabled?
-
PublicActivity.enabled?
-
end
-
#
-
# Shortcut for {ClassMethods#get_hook}
-
# @param (see ClassMethods#get_hook)
-
# @return (see ClassMethods#get_hook)
-
# @since (see ClassMethods#get_hook)
-
# @api (see ClassMethods#get_hook)
-
1
def get_hook(key)
-
9
self.class.get_hook(key)
-
end
-
-
# Calls hook safely.
-
# If a hook for given action exists, calls it with model (self) and
-
# controller (if available, see {StoreController})
-
# @param key (see #get_hook)
-
# @return [Boolean] if hook exists, it's decision, if there's no hook, true
-
# @since 0.4.0
-
# @api private
-
1
def call_hook_safe(key)
-
9
hook = self.get_hook(key)
-
9
if hook
-
# provides hook with model and controller
-
hook.call(self, PublicActivity.get_controller)
-
else
-
9
true
-
end
-
end
-
-
# Directly creates activity record in the database, based on supplied options.
-
#
-
# It's meant for creating custom activities while *preserving* *all*
-
# *configuration* defined before. If you fire up the simplest of options:
-
#
-
# current_user.create_activity(:avatar_changed)
-
#
-
# It will still gather data from any procs or symbols you passed as params
-
# to {Tracked::ClassMethods#tracked}. It will ask the hooks you defined
-
# whether to really save this activity.
-
#
-
# But you can also overwrite instance and global settings with your options:
-
#
-
# @article.activity :owner => proc {|controller| controller.current_user }
-
# @article.create_activity(:commented_on, :owner => @user)
-
#
-
# And it's smart! It won't execute your proc, since you've chosen to
-
# overwrite instance parameter _:owner_ with @user.
-
#
-
# [:key]
-
# The key will be generated from either:
-
# * the first parameter you pass that is not a hash (*action*)
-
# * the _:action_ option in the options hash (*action*)
-
# * the _:key_ option in the options hash (it has to be a full key,
-
# including model name)
-
# When you pass an *action* (first two options above), they will be
-
# added to parameterized model name:
-
#
-
# Given Article model and instance: @article,
-
#
-
# @article.create_activity :commented_on
-
# @article.activities.last.key # => "article.commented_on"
-
#
-
# For other parameters, see {Tracked#activity}, and "Instance options"
-
# accessors at {Tracked}, information on hooks is available at
-
# {Tracked::ClassMethods#tracked}.
-
# @see #prepare_settings
-
# @return [Model, nil] If created successfully, new activity
-
# @since 0.4.0
-
# @api public
-
# @overload create_activity(action, options = {})
-
# @param [Symbol,String] action Name of the action
-
# @param [Hash] options Options with quality higher than instance options
-
# set in {Tracked#activity}
-
# @option options [Activist] :owner Owner
-
# @option options [Activist] :recipient Recipient
-
# @option options [Hash] :params Parameters, see
-
# {PublicActivity.resolve_value}
-
# @overload create_activity(options = {})
-
# @param [Hash] options Options with quality higher than instance options
-
# set in {Tracked#activity}
-
# @option options [Symbol,String] :action Name of the action
-
# @option options [String] :key Full key
-
# @option options [Activist] :owner Owner
-
# @option options [Activist] :recipient Recipient
-
# @option options [Hash] :params Parameters, see
-
# {PublicActivity.resolve_value}
-
1
def create_activity(*args)
-
9
return unless self.public_activity_enabled?
-
9
options = prepare_settings(*args)
-
-
9
if call_hook_safe(options[:key].split('.').last)
-
9
reset_activity_instance_options
-
9
return PublicActivity::Adapter.create_activity(self, options)
-
end
-
-
nil
-
end
-
-
# Prepares settings used during creation of Activity record.
-
# params passed directly to tracked model have priority over
-
# settings specified in tracked() method
-
#
-
# @see #create_activity
-
# @return [Hash] Settings with preserved options that were passed
-
# @api private
-
# @overload prepare_settings(action, options = {})
-
# @see #create_activity
-
# @overload prepare_settings(options = {})
-
# @see #create_activity
-
1
def prepare_settings(*args)
-
9
raw_options = args.extract_options!
-
9
action = [args.first, raw_options.delete(:action)].compact.first
-
9
key = prepare_key(action, raw_options)
-
-
9
raise NoKeyProvided, "No key provided for #{self.class.name}" unless key
-
-
9
prepare_custom_fields(raw_options.except(:params)).merge(
-
{
-
key: key,
-
owner: prepare_relation(:owner, raw_options),
-
recipient: prepare_relation(:recipient, raw_options),
-
parameters: prepare_parameters(raw_options),
-
}
-
)
-
end
-
-
# Prepares and resolves custom fields
-
# users can pass to `tracked` method
-
# @private
-
1
def prepare_custom_fields(options)
-
9
customs = self.class.activity_custom_fields_global.clone
-
9
customs.merge!(self.activity_custom_fields) if self.activity_custom_fields
-
9
customs.merge!(options)
-
9
customs.each do |k, v|
-
39
customs[k] = PublicActivity.resolve_value(self, v)
-
end
-
end
-
-
# Prepares i18n parameters that will
-
# be serialized into the Activity#parameters column
-
# @private
-
1
def prepare_parameters(options)
-
9
params = {}
-
9
params.merge!(self.class.activity_params_global)
-
9
params.merge!(self.activity_params) if self.activity_params
-
9
params.merge!([options.delete(:parameters), options.delete(:params), {}].compact.first)
-
15
params.each { |k, v| params[k] = PublicActivity.resolve_value(self, v) }
-
end
-
-
# Prepares relation to be saved
-
# to Activity. Can be :recipient or :owner
-
# @private
-
1
def prepare_relation(name, options)
-
18
PublicActivity.resolve_value(self,
-
18
(options.has_key?(name) ? options[name] : (
-
self.send("activity_#{name}") || self.class.send("activity_#{name}_global")
-
)
-
)
-
)
-
end
-
-
# Helper method to serialize class name into relevant key
-
# @return [String] the resulted key
-
# @param [Symbol] or [String] the name of the operation to be done on class
-
# @param [Hash] options to be used on key generation, defaults to {}
-
1
def prepare_key(action, options = {})
-
(
-
options[:key] ||
-
9
self.activity_key ||
-
9
((self.class.name.underscore.gsub('/', '_') + "." + action.to_s) if action)
-
9
).try(:to_s)
-
end
-
-
# Resets all instance options on the object
-
# triggered by a successful #create_activity, should not be
-
# called from any other place, or from application code.
-
# @private
-
1
def reset_activity_instance_options
-
9
@activity_params = {}
-
9
@activity_key = nil
-
9
@activity_owner = nil
-
9
@activity_recipient = nil
-
9
@activity_custom_fields = {}
-
end
-
end
-
end
-
1
require 'singleton'
-
-
1
module PublicActivity
-
# Class used to initialize configuration object.
-
1
class Config
-
1
include ::Singleton
-
1
attr_accessor :enabled, :table_name
-
-
1
@@orm = :active_record
-
-
1
def initialize
-
# Indicates whether PublicActivity is enabled globally
-
1
@enabled = true
-
1
@table_name = "activities"
-
end
-
-
# Evaluates given block to provide DSL configuration.
-
# @example Initializer for Rails
-
# PublicActivity::Config.set do
-
# orm :mongo_mapper
-
# enabled false
-
# table_name "activities"
-
# end
-
1
def self.set &block
-
b = Block.new
-
b.instance_eval &block
-
@@orm = b.orm unless b.orm.nil?
-
instance
-
instance.instance_variable_set(:@enabled, b.enabled) unless b.enabled.nil?
-
end
-
-
# Set the ORM for use by PublicActivity.
-
1
def self.orm(orm = nil)
-
4
@@orm = (orm ? orm.to_sym : false) || @@orm
-
end
-
-
# alias for {#orm}
-
# @see #orm
-
1
def self.orm=(orm = nil)
-
orm(orm)
-
end
-
-
# instance version of {Config#orm}
-
# @see Config#orm
-
1
def orm(orm=nil)
-
4
self.class.orm(orm)
-
end
-
-
# Provides simple DSL for the config block.
-
1
class Block
-
1
attr_reader :orm, :enabled, :table_name
-
# @see Config#orm
-
1
def orm(orm = nil)
-
@orm = (orm ? orm.to_sym : false) || @orm
-
end
-
-
# Decides whether to enable PublicActivity.
-
# @param en [Boolean] Enabled?
-
1
def enabled(en = nil)
-
@enabled = (en.nil? ? @enabled : en)
-
end
-
-
# Sets the table_name
-
# for the model
-
1
def table_name(name = nil)
-
PublicActivity.config.table_name = name
-
end
-
end
-
end
-
end
-
1
module PublicActivity
-
# Provides helper methods for selecting activities from a user.
-
1
module Activist
-
# Delegates to configured ORM.
-
1
def self.included(base)
-
1
base.extend PublicActivity::inherit_orm("Activist")
-
end
-
end
-
end
-
1
module PublicActivity
-
1
class Activity < inherit_orm("Activity")
-
end
-
end
-
1
module PublicActivity
-
# Loads database-specific routines for use by PublicActivity.
-
1
class Adapter < inherit_orm("Adapter")
-
end
-
end
-
1
module PublicActivity
-
# Provides association for activities bound to this object by *trackable*.
-
1
module Trackable
-
# Delegates to ORM.
-
1
def self.included(base)
-
1
base.extend PublicActivity::inherit_orm("Trackable")
-
end
-
end
-
end
-
1
require 'active_record'
-
1
require_relative 'active_record/activity.rb'
-
1
require_relative 'active_record/adapter.rb'
-
1
require_relative 'active_record/activist.rb'
-
1
require_relative 'active_record/trackable.rb'
-
1
module PublicActivity
-
1
module ORM
-
1
module ActiveRecord
-
# Module extending classes that serve as owners
-
1
module Activist
-
# Adds ActiveRecord associations to model to simplify fetching
-
# so you can list activities performed by the owner.
-
# It is completely optional. Any model can be an owner to an activity
-
# even without being an explicit activist.
-
#
-
# == Usage:
-
# In model:
-
#
-
# class User < ActiveRecord::Base
-
# include PublicActivity::Model
-
# activist
-
# end
-
#
-
# In controller:
-
# User.first.activities
-
#
-
1
def activist
-
has_many :activities_as_owner,
-
:class_name => "::PublicActivity::Activity",
-
:as => :owner
-
has_many :activities_as_recipient,
-
:class_name => "::PublicActivity::Activity",
-
:as => :recipient
-
end
-
end
-
end
-
end
-
end
-
1
module PublicActivity
-
1
module ORM
-
1
module ActiveRecord
-
# The ActiveRecord model containing
-
# details about recorded activity.
-
1
class Activity < ::ActiveRecord::Base
-
1
include Renderable
-
1
self.table_name = PublicActivity.config.table_name
-
-
# Define polymorphic association to the parent
-
1
belongs_to :trackable, :polymorphic => true
-
# Define ownership to a resource responsible for this activity
-
1
belongs_to :owner, :polymorphic => true
-
# Define ownership to a resource targeted by this activity
-
1
belongs_to :recipient, :polymorphic => true
-
# Serialize parameters Hash
-
1
serialize :parameters, Hash
-
-
1
if ::ActiveRecord::VERSION::MAJOR < 4 || defined?(ProtectedAttributes)
-
attr_accessible :key, :owner, :parameters, :recipient, :trackable
-
end
-
end
-
end
-
end
-
end
-
1
module PublicActivity
-
1
module ORM
-
# Support for ActiveRecord for PublicActivity. Used by default and supported
-
# officialy.
-
1
module ActiveRecord
-
# Provides ActiveRecord specific, database-related routines for use by
-
# PublicActivity.
-
1
class Adapter
-
# Creates the activity on `trackable` with `options`
-
1
def self.create_activity(trackable, options)
-
9
trackable.activities.create options
-
end
-
end
-
end
-
end
-
end
-
1
module PublicActivity
-
1
module ORM
-
1
module ActiveRecord
-
# Implements {PublicActivity::Trackable} for ActiveRecord
-
# @see PublicActivity::Trackable
-
1
module Trackable
-
# Creates an association for activities where self is the *trackable*
-
# object.
-
1
def self.extended(base)
-
1
base.has_many :activities, :class_name => "::PublicActivity::Activity", :as => :trackable
-
end
-
end
-
end
-
end
-
end
-
1
module PublicActivity
-
# Provides logic for rendering activities. Handles both i18n strings
-
# support and smart partials rendering (different templates per activity key).
-
1
module Renderable
-
# Virtual attribute returning text description of the activity
-
# using the activity's key to translate using i18n.
-
1
def text(params = {})
-
# TODO: some helper for key transformation for two supported formats
-
k = key.split('.')
-
k.unshift('activity') if k.first != 'activity'
-
k = k.join('.')
-
-
I18n.t(k, parameters.merge(params) || {})
-
end
-
-
# Renders activity from views.
-
#
-
# @param [ActionView::Base] context
-
# @return [nil] nil
-
#
-
# Renders activity to the given ActionView context with included
-
# AV::Helpers::RenderingHelper (most commonly just ActionView::Base)
-
#
-
# The *preferred* *way* of rendering activities is
-
# to provide a template specifying how the rendering should be happening.
-
# However, one may choose using _I18n_ based approach when developing
-
# an application that supports plenty of languages.
-
#
-
# If partial view exists that matches the *key* attribute
-
# renders that partial with local variables set to contain both
-
# Activity and activity_parameters (hash with indifferent access)
-
#
-
# Otherwise, it outputs the I18n translation to the context
-
# @example Render a list of all activities from a view (erb)
-
# <ul>
-
# <% for activity in PublicActivity::Activity.all %>
-
# <li><%= render_activity(activity) %></li>
-
# <% end %>
-
# </ul>
-
#
-
# = Layouts
-
# You can supply a layout that will be used for activity partials
-
# with :layout param.
-
# Keep in mind that layouts for partials are also partials.
-
# @example Supply a layout
-
# # in views:
-
# # All examples look for a layout in app/views/layouts/_activity.erb
-
# render_activity @activity, :layout => "activity"
-
# render_activity @activity, :layout => "layouts/activity"
-
# render_activity @activity, :layout => :activity
-
#
-
# # app/views/layouts/_activity.erb
-
# <p><%= a.created_at %></p>
-
# <%= yield %>
-
#
-
# == Custom Layout Location
-
# You can customize the layout directory by supplying :layout_root
-
# or by using an absolute path.
-
#
-
# @example Declare custom layout location
-
#
-
# # Both examples look for a layout in "app/views/custom/_layout.erb"
-
#
-
# render_activity @activity, :layout_root => "custom"
-
# render_activity @activity, :layout => "/custom/layout"
-
#
-
# = Creating a template
-
# To use templates for formatting how the activity should render,
-
# create a template based on activity key, for example:
-
#
-
# Given a key _activity.article.create_, create directory tree
-
# _app/views/public_activity/article/_ and create the _create_ partial there
-
#
-
# Note that if a key consists of more than three parts splitted by commas, your
-
# directory structure will have to be deeper, for example:
-
# activity.article.comments.destroy => app/views/public_activity/articles/comments/_destroy.html.erb
-
#
-
# == Custom Directory
-
# You can override the default `public_directory` template root with the :root parameter
-
#
-
# @example Custom template root
-
# # look for templates inside of /app/views/custom instead of /app/views/public_directory
-
# render_activity @activity, :root => "custom"
-
#
-
# == Variables in templates
-
# From within a template there are two variables at your disposal:
-
# * activity (aliased as *a* for a shortcut)
-
# * params (aliased as *p*) [converted into a HashWithIndifferentAccess]
-
#
-
# @example Template for key: _activity.article.create_ (erb)
-
# <p>
-
# Article <strong><%= p[:name] %></strong>
-
# was written by <em><%= p["author"] %></em>
-
# <%= distance_of_time_in_words_to_now(a.created_at) %>
-
# </p>
-
1
def render(context, params = {})
-
partial_root = params.delete(:root) || 'public_activity'
-
partial_path = nil
-
layout_root = params.delete(:layout_root) || 'layouts'
-
-
if params.has_key? :display
-
if params[:display].to_sym == :"i18n"
-
return context.render :text => self.text(params)
-
else
-
partial_path = File.join(partial_root, params[:display].to_s)
-
end
-
end
-
-
context.render(
-
params.merge({
-
:partial => prepare_partial(partial_root, partial_path),
-
:layout => prepare_layout(layout_root, params.delete(:layout)),
-
:locals => prepare_locals(params)
-
})
-
)
-
end
-
-
1
def prepare_partial(root, path)
-
path || self.template_path(self.key, root)
-
end
-
-
1
def prepare_locals(params)
-
locals = params.delete(:locals) || Hash.new
-
-
controller = PublicActivity.get_controller
-
prepared_params = prepare_parameters(params)
-
locals.merge(
-
{
-
:a => self,
-
:activity => self,
-
:controller => controller,
-
:current_user => controller.respond_to?(:current_user) ? controller.current_user : nil,
-
:p => prepared_params,
-
:params => prepared_params
-
}
-
)
-
end
-
-
1
def prepare_layout(root, layout)
-
if layout
-
path = layout.to_s
-
unless path.starts_with?(root) || path.starts_with?("/")
-
return File.join(root, path)
-
end
-
end
-
layout
-
end
-
-
1
def prepare_parameters(params)
-
@prepared_params ||= self.parameters.with_indifferent_access.merge(params)
-
end
-
-
1
protected
-
-
# Builds the path to template based on activity key
-
1
def template_path(key, partial_root)
-
path = key.split(".")
-
path.delete_at(0) if path[0] == "activity"
-
path.unshift partial_root
-
path.join("/")
-
end
-
end
-
end
-
1
module PublicActivity
-
# Enables per-class disabling of PublicActivity functionality.
-
1
module Deactivatable
-
1
extend ActiveSupport::Concern
-
-
1
included do
-
1
class_attribute :public_activity_enabled_for_model
-
1
set_public_activity_class_defaults
-
end
-
-
# Returns true if PublicActivity is enabled
-
# globally and for this class.
-
# @return [Boolean]
-
# @api private
-
# @since 0.5.0
-
# overrides the method from Common
-
1
def public_activity_enabled?
-
9
PublicActivity.enabled? && self.class.public_activity_enabled_for_model
-
end
-
-
# Provides global methods to disable or enable PublicActivity on a per-class
-
# basis.
-
1
module ClassMethods
-
# Switches public_activity off for this class
-
1
def public_activity_off
-
self.public_activity_enabled_for_model = false
-
end
-
-
# Switches public_activity on for this class
-
1
def public_activity_on
-
self.public_activity_enabled_for_model = true
-
end
-
-
# @since 1.0.0
-
# @api private
-
1
def set_public_activity_class_defaults
-
1
super
-
1
self.public_activity_enabled_for_model = true
-
end
-
end
-
end
-
end
-
1
module PublicActivity
-
# Main module extending classes we want to keep track of.
-
1
module Tracked
-
1
extend ActiveSupport::Concern
-
# A shortcut method for setting custom key, owner and parameters of {Activity}
-
# in one line. Accepts a hash with 3 keys:
-
# :key, :owner, :params. You can specify all of them or just the ones you want to overwrite.
-
#
-
# == Options
-
#
-
# [:key]
-
# See {Common#activity_key}
-
# [:owner]
-
# See {Common#activity_owner}
-
# [:params]
-
# See {Common#activity_params}
-
# [:recipient]
-
# Set the recipient for this activity. Useful for private notifications, which should only be visible to a certain user. See {Common#activity_recipient}.
-
# @example
-
#
-
# @article = Article.new
-
# @article.title = "New article"
-
# @article.activity :key => "my.custom.article.key", :owner => @article.author, :params => {:title => @article.title}
-
# @article.save
-
# @article.activities.last.key #=> "my.custom.article.key"
-
# @article.activities.last.parameters #=> {:title => "New article"}
-
#
-
# @param options [Hash] instance options to set on the tracked model
-
# @return [nil]
-
1
def activity(options = {})
-
rest = options.clone
-
self.activity_key = rest.delete(:key) if rest[:key]
-
self.activity_owner = rest.delete(:owner) if rest[:owner]
-
self.activity_params = rest.delete(:params) if rest[:params]
-
self.activity_recipient = rest.delete(:recipient) if rest[:recipient]
-
self.activity_custom_fields = rest if rest.count > 0
-
nil
-
end
-
-
# Module with basic +tracked+ method that enables tracking models.
-
1
module ClassMethods
-
# Adds required callbacks for creating and updating
-
# tracked models and adds +activities+ relation for listing
-
# associated activities.
-
#
-
# == Parameters:
-
# [:owner]
-
# Specify the owner of the {Activity} (person responsible for the action).
-
# It can be a Proc, Symbol or an ActiveRecord object:
-
# == Examples:
-
#
-
# tracked :owner => :author
-
# tracked :owner => proc {|o| o.author}
-
#
-
# Keep in mind that owner relation is polymorphic, so you can't just
-
# provide id number of the owner object.
-
# [:recipient]
-
# Specify the recipient of the {Activity}
-
# It can be a Proc, Symbol, or an ActiveRecord object
-
# == Examples:
-
#
-
# tracked :recipient => :author
-
# tracked :recipient => proc {|o| o.author}
-
#
-
# Keep in mind that recipient relation is polymorphic, so you can't just
-
# provide id number of the owner object.
-
# [:params]
-
# Accepts a Hash with custom parameters you want to pass to i18n.translate
-
# method. It is later used in {Renderable#text} method.
-
# == Example:
-
# class Article < ActiveRecord::Base
-
# include PublicActivity::Model
-
# tracked :params => {
-
# :title => :title,
-
# :author_name => "Michael",
-
# :category_name => proc {|controller, model_instance| model_instance.category.name},
-
# :summary => proc {|controller, model_instance| truncate(model.text, :length => 30)}
-
# }
-
# end
-
#
-
# Values in the :params hash can either be an *exact* *value*, a *Proc/Lambda* executed before saving the activity or a *Symbol*
-
# which is a an attribute or a method name executed on the tracked model's instance.
-
#
-
# Everything specified here has a lower priority than parameters
-
# specified directly in {#activity} method.
-
# So treat it as a place where you provide 'default' values or where you
-
# specify what data should be gathered for every activity.
-
# For more dynamic settings refer to {Activity} model documentation.
-
# [:skip_defaults]
-
# Disables recording of activities on create/update/destroy leaving that to programmer's choice. Check {PublicActivity::Common#create_activity}
-
# for a guide on how to manually record activities.
-
# [:only]
-
# Accepts a symbol or an array of symbols, of which any combination of the three is accepted:
-
# * _:create_
-
# * _:update_
-
# * _:destroy_
-
# Selecting one or more of these will make PublicActivity create activities
-
# automatically for the tracked model on selected actions.
-
#
-
# Resulting activities will have have keys assigned to, respectively:
-
# * _article.create_
-
# * _article.update_
-
# * _article.destroy_
-
# Since only three options are valid,
-
# see _:except_ option for a shorter version
-
# [:except]
-
# Accepts a symbol or an array of symbols with values like in _:only_, above.
-
# Values provided will be subtracted from all default actions:
-
# (create, update, destroy).
-
#
-
# So, passing _create_ would track and automatically create
-
# activities on _update_ and _destroy_ actions,
-
# but not on the _create_ action.
-
# [:on]
-
# Accepts a Hash with key being the *action* on which to execute *value* (proc)
-
# Currently supported only for CRUD actions which are enabled in _:only_
-
# or _:except_ options on this method.
-
#
-
# Key-value pairs in this option define callbacks that can decide
-
# whether to create an activity or not. Procs have two attributes for
-
# use: _model_ and _controller_. If the proc returns true, the activity
-
# will be created, if not, then activity will not be saved.
-
#
-
# == Example:
-
# # app/models/article.rb
-
# tracked :on => {:update => proc {|model, controller| model.published? }}
-
#
-
# In the example above, given a model Article with boolean column _published_.
-
# The activities with key _article.update_ will only be created
-
# if the published status is set to true on that article.
-
# @param opts [Hash] options
-
# @return [nil] options
-
1
def tracked(opts = {})
-
options = opts.clone
-
-
include_default_actions(options)
-
-
assign_globals options
-
assign_hooks options
-
assign_custom_fields options
-
-
nil
-
end
-
-
1
def include_default_actions(options)
-
defaults = {
-
create: Creation,
-
destroy: Destruction,
-
update: Update
-
}
-
-
if options[:skip_defaults] == true
-
return
-
end
-
-
modules = if options[:except]
-
defaults.except(*options[:except])
-
elsif options[:only]
-
defaults.slice(*options[:only])
-
else
-
defaults
-
end
-
-
modules.each do |key, value|
-
include value
-
end
-
end
-
-
1
def available_options
-
[:skip_defaults, :only, :except, :on, :owner, :recipient, :params].freeze
-
end
-
-
1
def assign_globals(options)
-
[:owner, :recipient, :params].each do |key|
-
if options[key]
-
self.send("activity_#{key}_global=".to_sym, options.delete(key))
-
end
-
end
-
end
-
-
1
def assign_hooks(options)
-
if options[:on].is_a?(Hash)
-
self.activity_hooks = options[:on].select {|_, v| v.is_a? Proc}.symbolize_keys
-
end
-
end
-
-
1
def assign_custom_fields(options)
-
options.except(*available_options).each do |k, v|
-
self.activity_custom_fields_global[k] = v
-
end
-
end
-
end
-
end
-
end
-
1
require 'rack/utils'
-
1
require 'forwardable'
-
-
1
module Rack
-
# Rack::Lint validates your application and the requests and
-
# responses according to the Rack spec.
-
-
1
class Lint
-
1
def initialize(app)
-
@app = app
-
@content_length = nil
-
end
-
-
# :stopdoc:
-
-
1
class LintError < RuntimeError; end
-
1
module Assertion
-
1
def assert(message, &block)
-
unless block.call
-
raise LintError, message
-
end
-
end
-
end
-
1
include Assertion
-
-
## This specification aims to formalize the Rack protocol. You
-
## can (and should) use Rack::Lint to enforce it.
-
##
-
## When you develop middleware, be sure to add a Lint before and
-
## after to catch all mistakes.
-
-
## = Rack applications
-
-
## A Rack application is a Ruby object (not a class) that
-
## responds to +call+.
-
1
def call(env=nil)
-
dup._call(env)
-
end
-
-
1
def _call(env)
-
## It takes exactly one argument, the *environment*
-
assert("No env given") { env }
-
check_env env
-
-
env['rack.input'] = InputWrapper.new(env['rack.input'])
-
env['rack.errors'] = ErrorWrapper.new(env['rack.errors'])
-
-
## and returns an Array of exactly three values:
-
status, headers, @body = @app.call(env)
-
## The *status*,
-
check_status status
-
## the *headers*,
-
check_headers headers
-
-
check_hijack_response headers, env
-
-
## and the *body*.
-
check_content_type status, headers
-
check_content_length status, headers
-
@head_request = env[REQUEST_METHOD] == "HEAD"
-
[status, headers, self]
-
end
-
-
## == The Environment
-
1
def check_env(env)
-
## The environment must be an instance of Hash that includes
-
## CGI-like headers. The application is free to modify the
-
## environment.
-
assert("env #{env.inspect} is not a Hash, but #{env.class}") {
-
env.kind_of? Hash
-
}
-
-
##
-
## The environment is required to include these variables
-
## (adopted from PEP333), except when they'd be empty, but see
-
## below.
-
-
## <tt>REQUEST_METHOD</tt>:: The HTTP request method, such as
-
## "GET" or "POST". This cannot ever
-
## be an empty string, and so is
-
## always required.
-
-
## <tt>SCRIPT_NAME</tt>:: The initial portion of the request
-
## URL's "path" that corresponds to the
-
## application object, so that the
-
## application knows its virtual
-
## "location". This may be an empty
-
## string, if the application corresponds
-
## to the "root" of the server.
-
-
## <tt>PATH_INFO</tt>:: The remainder of the request URL's
-
## "path", designating the virtual
-
## "location" of the request's target
-
## within the application. This may be an
-
## empty string, if the request URL targets
-
## the application root and does not have a
-
## trailing slash. This value may be
-
## percent-encoded when I originating from
-
## a URL.
-
-
## <tt>QUERY_STRING</tt>:: The portion of the request URL that
-
## follows the <tt>?</tt>, if any. May be
-
## empty, but is always required!
-
-
## <tt>SERVER_NAME</tt>, <tt>SERVER_PORT</tt>::
-
## When combined with <tt>SCRIPT_NAME</tt> and
-
## <tt>PATH_INFO</tt>, these variables can be
-
## used to complete the URL. Note, however,
-
## that <tt>HTTP_HOST</tt>, if present,
-
## should be used in preference to
-
## <tt>SERVER_NAME</tt> for reconstructing
-
## the request URL.
-
## <tt>SERVER_NAME</tt> and <tt>SERVER_PORT</tt>
-
## can never be empty strings, and so
-
## are always required.
-
-
## <tt>HTTP_</tt> Variables:: Variables corresponding to the
-
## client-supplied HTTP request
-
## headers (i.e., variables whose
-
## names begin with <tt>HTTP_</tt>). The
-
## presence or absence of these
-
## variables should correspond with
-
## the presence or absence of the
-
## appropriate HTTP header in the
-
## request. See
-
## <a href="https://tools.ietf.org/html/rfc3875#section-4.1.18">
-
## RFC3875 section 4.1.18</a> for
-
## specific behavior.
-
-
## In addition to this, the Rack environment must include these
-
## Rack-specific variables:
-
-
## <tt>rack.version</tt>:: The Array representing this version of Rack
-
## See Rack::VERSION, that corresponds to
-
## the version of this SPEC.
-
-
## <tt>rack.url_scheme</tt>:: +http+ or +https+, depending on the
-
## request URL.
-
-
## <tt>rack.input</tt>:: See below, the input stream.
-
-
## <tt>rack.errors</tt>:: See below, the error stream.
-
-
## <tt>rack.multithread</tt>:: true if the application object may be
-
## simultaneously invoked by another thread
-
## in the same process, false otherwise.
-
-
## <tt>rack.multiprocess</tt>:: true if an equivalent application object
-
## may be simultaneously invoked by another
-
## process, false otherwise.
-
-
## <tt>rack.run_once</tt>:: true if the server expects
-
## (but does not guarantee!) that the
-
## application will only be invoked this one
-
## time during the life of its containing
-
## process. Normally, this will only be true
-
## for a server based on CGI
-
## (or something similar).
-
-
## <tt>rack.hijack?</tt>:: present and true if the server supports
-
## connection hijacking. See below, hijacking.
-
-
## <tt>rack.hijack</tt>:: an object responding to #call that must be
-
## called at least once before using
-
## rack.hijack_io.
-
## It is recommended #call return rack.hijack_io
-
## as well as setting it in env if necessary.
-
-
## <tt>rack.hijack_io</tt>:: if rack.hijack? is true, and rack.hijack
-
## has received #call, this will contain
-
## an object resembling an IO. See hijacking.
-
-
## Additional environment specifications have approved to
-
## standardized middleware APIs. None of these are required to
-
## be implemented by the server.
-
-
## <tt>rack.session</tt>:: A hash like interface for storing
-
## request session data.
-
## The store must implement:
-
if session = env['rack.session']
-
## store(key, value) (aliased as []=);
-
assert("session #{session.inspect} must respond to store and []=") {
-
session.respond_to?(:store) && session.respond_to?(:[]=)
-
}
-
-
## fetch(key, default = nil) (aliased as []);
-
assert("session #{session.inspect} must respond to fetch and []") {
-
session.respond_to?(:fetch) && session.respond_to?(:[])
-
}
-
-
## delete(key);
-
assert("session #{session.inspect} must respond to delete") {
-
session.respond_to?(:delete)
-
}
-
-
## clear;
-
assert("session #{session.inspect} must respond to clear") {
-
session.respond_to?(:clear)
-
}
-
end
-
-
## <tt>rack.logger</tt>:: A common object interface for logging messages.
-
## The object must implement:
-
if logger = env['rack.logger']
-
## info(message, &block)
-
assert("logger #{logger.inspect} must respond to info") {
-
logger.respond_to?(:info)
-
}
-
-
## debug(message, &block)
-
assert("logger #{logger.inspect} must respond to debug") {
-
logger.respond_to?(:debug)
-
}
-
-
## warn(message, &block)
-
assert("logger #{logger.inspect} must respond to warn") {
-
logger.respond_to?(:warn)
-
}
-
-
## error(message, &block)
-
assert("logger #{logger.inspect} must respond to error") {
-
logger.respond_to?(:error)
-
}
-
-
## fatal(message, &block)
-
assert("logger #{logger.inspect} must respond to fatal") {
-
logger.respond_to?(:fatal)
-
}
-
end
-
-
## <tt>rack.multipart.buffer_size</tt>:: An Integer hint to the multipart parser as to what chunk size to use for reads and writes.
-
if bufsize = env['rack.multipart.buffer_size']
-
assert("rack.multipart.buffer_size must be an Integer > 0 if specified") {
-
bufsize.is_a?(Integer) && bufsize > 0
-
}
-
end
-
-
## <tt>rack.multipart.tempfile_factory</tt>:: An object responding to #call with two arguments, the filename and content_type given for the multipart form field, and returning an IO-like object that responds to #<< and optionally #rewind. This factory will be used to instantiate the tempfile for each multipart form file upload field, rather than the default class of Tempfile.
-
if tempfile_factory = env['rack.multipart.tempfile_factory']
-
assert("rack.multipart.tempfile_factory must respond to #call") { tempfile_factory.respond_to?(:call) }
-
env['rack.multipart.tempfile_factory'] = lambda do |filename, content_type|
-
io = tempfile_factory.call(filename, content_type)
-
assert("rack.multipart.tempfile_factory return value must respond to #<<") { io.respond_to?(:<<) }
-
io
-
end
-
end
-
-
## The server or the application can store their own data in the
-
## environment, too. The keys must contain at least one dot,
-
## and should be prefixed uniquely. The prefix <tt>rack.</tt>
-
## is reserved for use with the Rack core distribution and other
-
## accepted specifications and must not be used otherwise.
-
##
-
-
%w[REQUEST_METHOD SERVER_NAME SERVER_PORT
-
QUERY_STRING
-
rack.version rack.input rack.errors
-
rack.multithread rack.multiprocess rack.run_once].each { |header|
-
assert("env missing required key #{header}") { env.include? header }
-
}
-
-
## The environment must not contain the keys
-
## <tt>HTTP_CONTENT_TYPE</tt> or <tt>HTTP_CONTENT_LENGTH</tt>
-
## (use the versions without <tt>HTTP_</tt>).
-
%w[HTTP_CONTENT_TYPE HTTP_CONTENT_LENGTH].each { |header|
-
assert("env contains #{header}, must use #{header[5,-1]}") {
-
not env.include? header
-
}
-
}
-
-
## The CGI keys (named without a period) must have String values.
-
env.each { |key, value|
-
next if key.include? "." # Skip extensions
-
assert("env variable #{key} has non-string value #{value.inspect}") {
-
value.kind_of? String
-
}
-
}
-
-
## There are the following restrictions:
-
-
## * <tt>rack.version</tt> must be an array of Integers.
-
assert("rack.version must be an Array, was #{env["rack.version"].class}") {
-
env["rack.version"].kind_of? Array
-
}
-
## * <tt>rack.url_scheme</tt> must either be +http+ or +https+.
-
assert("rack.url_scheme unknown: #{env["rack.url_scheme"].inspect}") {
-
%w[http https].include? env["rack.url_scheme"]
-
}
-
-
## * There must be a valid input stream in <tt>rack.input</tt>.
-
check_input env["rack.input"]
-
## * There must be a valid error stream in <tt>rack.errors</tt>.
-
check_error env["rack.errors"]
-
## * There may be a valid hijack stream in <tt>rack.hijack_io</tt>
-
check_hijack env
-
-
## * The <tt>REQUEST_METHOD</tt> must be a valid token.
-
assert("REQUEST_METHOD unknown: #{env[REQUEST_METHOD]}") {
-
env["REQUEST_METHOD"] =~ /\A[0-9A-Za-z!\#$%&'*+.^_`|~-]+\z/
-
}
-
-
## * The <tt>SCRIPT_NAME</tt>, if non-empty, must start with <tt>/</tt>
-
assert("SCRIPT_NAME must start with /") {
-
!env.include?("SCRIPT_NAME") ||
-
env["SCRIPT_NAME"] == "" ||
-
env["SCRIPT_NAME"] =~ /\A\//
-
}
-
## * The <tt>PATH_INFO</tt>, if non-empty, must start with <tt>/</tt>
-
assert("PATH_INFO must start with /") {
-
!env.include?("PATH_INFO") ||
-
env["PATH_INFO"] == "" ||
-
env["PATH_INFO"] =~ /\A\//
-
}
-
## * The <tt>CONTENT_LENGTH</tt>, if given, must consist of digits only.
-
assert("Invalid CONTENT_LENGTH: #{env["CONTENT_LENGTH"]}") {
-
!env.include?("CONTENT_LENGTH") || env["CONTENT_LENGTH"] =~ /\A\d+\z/
-
}
-
-
## * One of <tt>SCRIPT_NAME</tt> or <tt>PATH_INFO</tt> must be
-
## set. <tt>PATH_INFO</tt> should be <tt>/</tt> if
-
## <tt>SCRIPT_NAME</tt> is empty.
-
assert("One of SCRIPT_NAME or PATH_INFO must be set (make PATH_INFO '/' if SCRIPT_NAME is empty)") {
-
env["SCRIPT_NAME"] || env["PATH_INFO"]
-
}
-
## <tt>SCRIPT_NAME</tt> never should be <tt>/</tt>, but instead be empty.
-
assert("SCRIPT_NAME cannot be '/', make it '' and PATH_INFO '/'") {
-
env["SCRIPT_NAME"] != "/"
-
}
-
end
-
-
## === The Input Stream
-
##
-
## The input stream is an IO-like object which contains the raw HTTP
-
## POST data.
-
1
def check_input(input)
-
## When applicable, its external encoding must be "ASCII-8BIT" and it
-
## must be opened in binary mode, for Ruby 1.9 compatibility.
-
assert("rack.input #{input} does not have ASCII-8BIT as its external encoding") {
-
input.external_encoding.name == "ASCII-8BIT"
-
} if input.respond_to?(:external_encoding)
-
assert("rack.input #{input} is not opened in binary mode") {
-
input.binmode?
-
} if input.respond_to?(:binmode?)
-
-
## The input stream must respond to +gets+, +each+, +read+ and +rewind+.
-
[:gets, :each, :read, :rewind].each { |method|
-
assert("rack.input #{input} does not respond to ##{method}") {
-
input.respond_to? method
-
}
-
}
-
end
-
-
1
class InputWrapper
-
1
include Assertion
-
-
1
def initialize(input)
-
@input = input
-
end
-
-
## * +gets+ must be called without arguments and return a string,
-
## or +nil+ on EOF.
-
1
def gets(*args)
-
assert("rack.input#gets called with arguments") { args.size == 0 }
-
v = @input.gets
-
assert("rack.input#gets didn't return a String") {
-
v.nil? or v.kind_of? String
-
}
-
v
-
end
-
-
## * +read+ behaves like IO#read.
-
## Its signature is <tt>read([length, [buffer]])</tt>.
-
##
-
## If given, +length+ must be a non-negative Integer (>= 0) or +nil+,
-
## and +buffer+ must be a String and may not be nil.
-
##
-
## If +length+ is given and not nil, then this method reads at most
-
## +length+ bytes from the input stream.
-
##
-
## If +length+ is not given or nil, then this method reads
-
## all data until EOF.
-
##
-
## When EOF is reached, this method returns nil if +length+ is given
-
## and not nil, or "" if +length+ is not given or is nil.
-
##
-
## If +buffer+ is given, then the read data will be placed
-
## into +buffer+ instead of a newly created String object.
-
1
def read(*args)
-
assert("rack.input#read called with too many arguments") {
-
args.size <= 2
-
}
-
if args.size >= 1
-
assert("rack.input#read called with non-integer and non-nil length") {
-
args.first.kind_of?(Integer) || args.first.nil?
-
}
-
assert("rack.input#read called with a negative length") {
-
args.first.nil? || args.first >= 0
-
}
-
end
-
if args.size >= 2
-
assert("rack.input#read called with non-String buffer") {
-
args[1].kind_of?(String)
-
}
-
end
-
-
v = @input.read(*args)
-
-
assert("rack.input#read didn't return nil or a String") {
-
v.nil? or v.kind_of? String
-
}
-
if args[0].nil?
-
assert("rack.input#read(nil) returned nil on EOF") {
-
!v.nil?
-
}
-
end
-
-
v
-
end
-
-
## * +each+ must be called without arguments and only yield Strings.
-
1
def each(*args)
-
assert("rack.input#each called with arguments") { args.size == 0 }
-
@input.each { |line|
-
assert("rack.input#each didn't yield a String") {
-
line.kind_of? String
-
}
-
yield line
-
}
-
end
-
-
## * +rewind+ must be called without arguments. It rewinds the input
-
## stream back to the beginning. It must not raise Errno::ESPIPE:
-
## that is, it may not be a pipe or a socket. Therefore, handler
-
## developers must buffer the input data into some rewindable object
-
## if the underlying input stream is not rewindable.
-
1
def rewind(*args)
-
assert("rack.input#rewind called with arguments") { args.size == 0 }
-
assert("rack.input#rewind raised Errno::ESPIPE") {
-
begin
-
@input.rewind
-
true
-
rescue Errno::ESPIPE
-
false
-
end
-
}
-
end
-
-
## * +close+ must never be called on the input stream.
-
1
def close(*args)
-
assert("rack.input#close must not be called") { false }
-
end
-
end
-
-
## === The Error Stream
-
1
def check_error(error)
-
## The error stream must respond to +puts+, +write+ and +flush+.
-
[:puts, :write, :flush].each { |method|
-
assert("rack.error #{error} does not respond to ##{method}") {
-
error.respond_to? method
-
}
-
}
-
end
-
-
1
class ErrorWrapper
-
1
include Assertion
-
-
1
def initialize(error)
-
@error = error
-
end
-
-
## * +puts+ must be called with a single argument that responds to +to_s+.
-
1
def puts(str)
-
@error.puts str
-
end
-
-
## * +write+ must be called with a single argument that is a String.
-
1
def write(str)
-
assert("rack.errors#write not called with a String") { str.kind_of? String }
-
@error.write str
-
end
-
-
## * +flush+ must be called without arguments and must be called
-
## in order to make the error appear for sure.
-
1
def flush
-
@error.flush
-
end
-
-
## * +close+ must never be called on the error stream.
-
1
def close(*args)
-
assert("rack.errors#close must not be called") { false }
-
end
-
end
-
-
1
class HijackWrapper
-
1
include Assertion
-
1
extend Forwardable
-
-
1
REQUIRED_METHODS = [
-
:read, :write, :read_nonblock, :write_nonblock, :flush, :close,
-
:close_read, :close_write, :closed?
-
]
-
-
1
def_delegators :@io, *REQUIRED_METHODS
-
-
1
def initialize(io)
-
@io = io
-
REQUIRED_METHODS.each do |meth|
-
assert("rack.hijack_io must respond to #{meth}") { io.respond_to? meth }
-
end
-
end
-
end
-
-
## === Hijacking
-
#
-
# AUTHORS: n.b. The trailing whitespace between paragraphs is important and
-
# should not be removed. The whitespace creates paragraphs in the RDoc
-
# output.
-
#
-
## ==== Request (before status)
-
1
def check_hijack(env)
-
if env['rack.hijack?']
-
## If rack.hijack? is true then rack.hijack must respond to #call.
-
original_hijack = env['rack.hijack']
-
assert("rack.hijack must respond to call") { original_hijack.respond_to?(:call) }
-
env['rack.hijack'] = proc do
-
## rack.hijack must return the io that will also be assigned (or is
-
## already present, in rack.hijack_io.
-
io = original_hijack.call
-
HijackWrapper.new(io)
-
##
-
## rack.hijack_io must respond to:
-
## <tt>read, write, read_nonblock, write_nonblock, flush, close,
-
## close_read, close_write, closed?</tt>
-
##
-
## The semantics of these IO methods must be a best effort match to
-
## those of a normal ruby IO or Socket object, using standard
-
## arguments and raising standard exceptions. Servers are encouraged
-
## to simply pass on real IO objects, although it is recognized that
-
## this approach is not directly compatible with SPDY and HTTP 2.0.
-
##
-
## IO provided in rack.hijack_io should preference the
-
## IO::WaitReadable and IO::WaitWritable APIs wherever supported.
-
##
-
## There is a deliberate lack of full specification around
-
## rack.hijack_io, as semantics will change from server to server.
-
## Users are encouraged to utilize this API with a knowledge of their
-
## server choice, and servers may extend the functionality of
-
## hijack_io to provide additional features to users. The purpose of
-
## rack.hijack is for Rack to "get out of the way", as such, Rack only
-
## provides the minimum of specification and support.
-
env['rack.hijack_io'] = HijackWrapper.new(env['rack.hijack_io'])
-
io
-
end
-
else
-
##
-
## If rack.hijack? is false, then rack.hijack should not be set.
-
assert("rack.hijack? is false, but rack.hijack is present") { env['rack.hijack'].nil? }
-
##
-
## If rack.hijack? is false, then rack.hijack_io should not be set.
-
assert("rack.hijack? is false, but rack.hijack_io is present") { env['rack.hijack_io'].nil? }
-
end
-
end
-
-
## ==== Response (after headers)
-
## It is also possible to hijack a response after the status and headers
-
## have been sent.
-
1
def check_hijack_response(headers, env)
-
-
# this check uses headers like a hash, but the spec only requires
-
# headers respond to #each
-
headers = Rack::Utils::HeaderHash.new(headers)
-
-
## In order to do this, an application may set the special header
-
## <tt>rack.hijack</tt> to an object that responds to <tt>call</tt>
-
## accepting an argument that conforms to the <tt>rack.hijack_io</tt>
-
## protocol.
-
##
-
## After the headers have been sent, and this hijack callback has been
-
## called, the application is now responsible for the remaining lifecycle
-
## of the IO. The application is also responsible for maintaining HTTP
-
## semantics. Of specific note, in almost all cases in the current SPEC,
-
## applications will have wanted to specify the header Connection:close in
-
## HTTP/1.1, and not Connection:keep-alive, as there is no protocol for
-
## returning hijacked sockets to the web server. For that purpose, use the
-
## body streaming API instead (progressively yielding strings via each).
-
##
-
## Servers must ignore the <tt>body</tt> part of the response tuple when
-
## the <tt>rack.hijack</tt> response API is in use.
-
-
if env['rack.hijack?'] && headers['rack.hijack']
-
assert('rack.hijack header must respond to #call') {
-
headers['rack.hijack'].respond_to? :call
-
}
-
original_hijack = headers['rack.hijack']
-
headers['rack.hijack'] = proc do |io|
-
original_hijack.call HijackWrapper.new(io)
-
end
-
else
-
##
-
## The special response header <tt>rack.hijack</tt> must only be set
-
## if the request env has <tt>rack.hijack?</tt> <tt>true</tt>.
-
assert('rack.hijack header must not be present if server does not support hijacking') {
-
headers['rack.hijack'].nil?
-
}
-
end
-
end
-
## ==== Conventions
-
## * Middleware should not use hijack unless it is handling the whole
-
## response.
-
## * Middleware may wrap the IO object for the response pattern.
-
## * Middleware should not wrap the IO object for the request pattern. The
-
## request pattern is intended to provide the hijacker with "raw tcp".
-
-
## == The Response
-
-
## === The Status
-
1
def check_status(status)
-
## This is an HTTP status. When parsed as integer (+to_i+), it must be
-
## greater than or equal to 100.
-
assert("Status must be >=100 seen as integer") { status.to_i >= 100 }
-
end
-
-
## === The Headers
-
1
def check_headers(header)
-
## The header must respond to +each+, and yield values of key and value.
-
assert("headers object should respond to #each, but doesn't (got #{header.class} as headers)") {
-
header.respond_to? :each
-
}
-
header.each { |key, value|
-
## Special headers starting "rack." are for communicating with the
-
## server, and must not be sent back to the client.
-
next if key =~ /^rack\..+$/
-
-
## The header keys must be Strings.
-
assert("header key must be a string, was #{key.class}") {
-
key.kind_of? String
-
}
-
## The header must not contain a +Status+ key.
-
assert("header must not contain Status") { key.downcase != "status" }
-
## The header must conform to RFC7230 token specification, i.e. cannot
-
## contain non-printable ASCII, DQUOTE or "(),/:;<=>?@[\]{}".
-
assert("invalid header name: #{key}") { key !~ /[\(\),\/:;<=>\?@\[\\\]{}[:cntrl:]]/ }
-
-
## The values of the header must be Strings,
-
assert("a header value must be a String, but the value of " +
-
"'#{key}' is a #{value.class}") { value.kind_of? String }
-
## consisting of lines (for multiple header values, e.g. multiple
-
## <tt>Set-Cookie</tt> values) separated by "\\n".
-
value.split("\n").each { |item|
-
## The lines must not contain characters below 037.
-
assert("invalid header value #{key}: #{item.inspect}") {
-
item !~ /[\000-\037]/
-
}
-
}
-
}
-
end
-
-
## === The Content-Type
-
1
def check_content_type(status, headers)
-
headers.each { |key, value|
-
## There must not be a <tt>Content-Type</tt>, when the +Status+ is 1xx,
-
## 204, 205 or 304.
-
if key.downcase == "content-type"
-
assert("Content-Type header found in #{status} response, not allowed") {
-
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
-
}
-
return
-
end
-
}
-
end
-
-
## === The Content-Length
-
1
def check_content_length(status, headers)
-
headers.each { |key, value|
-
if key.downcase == 'content-length'
-
## There must not be a <tt>Content-Length</tt> header when the
-
## +Status+ is 1xx, 204, 205 or 304.
-
assert("Content-Length header found in #{status} response, not allowed") {
-
not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i
-
}
-
@content_length = value
-
end
-
}
-
end
-
-
1
def verify_content_length(bytes)
-
if @head_request
-
assert("Response body was given for HEAD request, but should be empty") {
-
bytes == 0
-
}
-
elsif @content_length
-
assert("Content-Length header was #{@content_length}, but should be #{bytes}") {
-
@content_length == bytes.to_s
-
}
-
end
-
end
-
-
## === The Body
-
1
def each
-
@closed = false
-
bytes = 0
-
-
## The Body must respond to +each+
-
assert("Response body must respond to each") do
-
@body.respond_to?(:each)
-
end
-
-
@body.each { |part|
-
## and must only yield String values.
-
assert("Body yielded non-string value #{part.inspect}") {
-
part.kind_of? String
-
}
-
bytes += Rack::Utils.bytesize(part)
-
yield part
-
}
-
verify_content_length(bytes)
-
-
##
-
## The Body itself should not be an instance of String, as this will
-
## break in Ruby 1.9.
-
##
-
## If the Body responds to +close+, it will be called after iteration. If
-
## the body is replaced by a middleware after action, the original body
-
## must be closed first, if it responds to close.
-
# XXX howto: assert("Body has not been closed") { @closed }
-
-
-
##
-
## If the Body responds to +to_path+, it must return a String
-
## identifying the location of a file whose contents are identical
-
## to that produced by calling +each+; this may be used by the
-
## server as an alternative, possibly more efficient way to
-
## transport the response.
-
-
if @body.respond_to?(:to_path)
-
assert("The file identified by body.to_path does not exist") {
-
::File.exist? @body.to_path
-
}
-
end
-
-
##
-
## The Body commonly is an Array of Strings, the application
-
## instance itself, or a File-like object.
-
end
-
-
1
def close
-
@closed = true
-
@body.close if @body.respond_to?(:close)
-
end
-
-
# :startdoc:
-
-
end
-
end
-
-
## == Thanks
-
## Some parts of this specification are adopted from PEP333: Python
-
## Web Server Gateway Interface
-
## v1.0 (http://www.python.org/dev/peps/pep-0333/). I'd like to thank
-
## everyone involved in that effort.
-
1
require 'uri'
-
1
require 'stringio'
-
1
require 'rack'
-
1
require 'rack/lint'
-
1
require 'rack/utils'
-
1
require 'rack/response'
-
-
1
module Rack
-
# Rack::MockRequest helps testing your Rack application without
-
# actually using HTTP.
-
#
-
# After performing a request on a URL with get/post/put/patch/delete, it
-
# returns a MockResponse with useful helper methods for effective
-
# testing.
-
#
-
# You can pass a hash with additional configuration to the
-
# get/post/put/patch/delete.
-
# <tt>:input</tt>:: A String or IO-like to be used as rack.input.
-
# <tt>:fatal</tt>:: Raise a FatalWarning if the app writes to rack.errors.
-
# <tt>:lint</tt>:: If true, wrap the application in a Rack::Lint.
-
-
1
class MockRequest
-
1
class FatalWarning < RuntimeError
-
end
-
-
1
class FatalWarner
-
1
def puts(warning)
-
raise FatalWarning, warning
-
end
-
-
1
def write(warning)
-
raise FatalWarning, warning
-
end
-
-
1
def flush
-
end
-
-
1
def string
-
""
-
end
-
end
-
-
1
DEFAULT_ENV = {
-
"rack.version" => Rack::VERSION,
-
"rack.input" => StringIO.new,
-
"rack.errors" => StringIO.new,
-
"rack.multithread" => true,
-
"rack.multiprocess" => true,
-
"rack.run_once" => false,
-
}
-
-
1
def initialize(app)
-
@app = app
-
end
-
-
1
def get(uri, opts={}) request("GET", uri, opts) end
-
1
def post(uri, opts={}) request("POST", uri, opts) end
-
1
def put(uri, opts={}) request("PUT", uri, opts) end
-
1
def patch(uri, opts={}) request("PATCH", uri, opts) end
-
1
def delete(uri, opts={}) request("DELETE", uri, opts) end
-
1
def head(uri, opts={}) request("HEAD", uri, opts) end
-
1
def options(uri, opts={}) request("OPTIONS", uri, opts) end
-
-
1
def request(method="GET", uri="", opts={})
-
env = self.class.env_for(uri, opts.merge(:method => method))
-
-
if opts[:lint]
-
app = Rack::Lint.new(@app)
-
else
-
app = @app
-
end
-
-
errors = env["rack.errors"]
-
status, headers, body = app.call(env)
-
MockResponse.new(status, headers, body, errors)
-
ensure
-
body.close if body.respond_to?(:close)
-
end
-
-
# For historical reasons, we're pinning to RFC 2396. It's easier for users
-
# and we get support from ruby 1.8 to 2.2 using this method.
-
1
def self.parse_uri_rfc2396(uri)
-
1
@parser ||= defined?(URI::RFC2396_Parser) ? URI::RFC2396_Parser.new : URI
-
1
@parser.parse(uri)
-
end
-
-
# Return the Rack environment used for a request to +uri+.
-
1
def self.env_for(uri="", opts={})
-
1
uri = parse_uri_rfc2396(uri)
-
1
uri.path = "/#{uri.path}" unless uri.path[0] == ?/
-
-
1
env = DEFAULT_ENV.dup
-
-
1
env[REQUEST_METHOD] = opts[:method] ? opts[:method].to_s.upcase : "GET"
-
1
env["SERVER_NAME"] = uri.host || "example.org"
-
1
env["SERVER_PORT"] = uri.port ? uri.port.to_s : "80"
-
1
env[QUERY_STRING] = uri.query.to_s
-
1
env[PATH_INFO] = (!uri.path || uri.path.empty?) ? "/" : uri.path
-
1
env["rack.url_scheme"] = uri.scheme || "http"
-
1
env["HTTPS"] = env["rack.url_scheme"] == "https" ? "on" : "off"
-
-
1
env[SCRIPT_NAME] = opts[:script_name] || ""
-
-
1
if opts[:fatal]
-
env["rack.errors"] = FatalWarner.new
-
else
-
1
env["rack.errors"] = StringIO.new
-
end
-
-
1
if params = opts[:params]
-
if env[REQUEST_METHOD] == "GET"
-
params = Utils.parse_nested_query(params) if params.is_a?(String)
-
params.update(Utils.parse_nested_query(env[QUERY_STRING]))
-
env[QUERY_STRING] = Utils.build_nested_query(params)
-
elsif !opts.has_key?(:input)
-
opts["CONTENT_TYPE"] = "application/x-www-form-urlencoded"
-
if params.is_a?(Hash)
-
if data = Utils::Multipart.build_multipart(params)
-
opts[:input] = data
-
opts["CONTENT_LENGTH"] ||= data.length.to_s
-
opts["CONTENT_TYPE"] = "multipart/form-data; boundary=#{Utils::Multipart::MULTIPART_BOUNDARY}"
-
else
-
opts[:input] = Utils.build_nested_query(params)
-
end
-
else
-
opts[:input] = params
-
end
-
end
-
end
-
-
1
empty_str = ""
-
1
empty_str.force_encoding("ASCII-8BIT") if empty_str.respond_to? :force_encoding
-
1
opts[:input] ||= empty_str
-
1
if String === opts[:input]
-
1
rack_input = StringIO.new(opts[:input])
-
else
-
rack_input = opts[:input]
-
end
-
-
1
rack_input.set_encoding(Encoding::BINARY) if rack_input.respond_to?(:set_encoding)
-
1
env['rack.input'] = rack_input
-
-
1
env["CONTENT_LENGTH"] ||= env["rack.input"].length.to_s
-
-
1
opts.each { |field, value|
-
4
env[field] = value if String === field
-
}
-
-
1
env
-
end
-
end
-
-
# Rack::MockResponse provides useful helpers for testing your apps.
-
# Usually, you don't create the MockResponse on your own, but use
-
# MockRequest.
-
-
1
class MockResponse < Rack::Response
-
# Headers
-
1
attr_reader :original_headers
-
-
# Errors
-
1
attr_accessor :errors
-
-
1
def initialize(status, headers, body, errors=StringIO.new(""))
-
@original_headers = headers
-
@errors = errors.string if errors.respond_to?(:string)
-
@body_string = nil
-
-
super(body, status, headers)
-
end
-
-
1
def =~(other)
-
body =~ other
-
end
-
-
1
def match(other)
-
body.match other
-
end
-
-
1
def body
-
# FIXME: apparently users of MockResponse expect the return value of
-
# MockResponse#body to be a string. However, the real response object
-
# returns the body as a list.
-
#
-
# See spec_showstatus.rb:
-
#
-
# should "not replace existing messages" do
-
# ...
-
# res.body.should == "foo!"
-
# end
-
super.join
-
end
-
-
1
def empty?
-
[201, 204, 205, 304].include? status
-
end
-
end
-
end
-
1
require 'active_support/dependencies/autoload'
-
1
$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/html-scanner"
-
-
1
module HTML
-
1
extend ActiveSupport::Autoload
-
-
1
eager_autoload do
-
1
autoload :CDATA, 'html/node'
-
1
autoload :Document, 'html/document'
-
1
autoload :FullSanitizer, 'html/sanitizer'
-
1
autoload :LinkSanitizer, 'html/sanitizer'
-
1
autoload :Node, 'html/node'
-
1
autoload :Sanitizer, 'html/sanitizer'
-
1
autoload :Selector, 'html/selector'
-
1
autoload :Tag, 'html/node'
-
1
autoload :Text, 'html/node'
-
1
autoload :Tokenizer, 'html/tokenizer'
-
1
autoload :Version, 'html/version'
-
1
autoload :WhiteListSanitizer, 'html/sanitizer'
-
end
-
end
-
1
require 'rails/dom/testing/assertions'
-
1
require 'active_support/concern'
-
1
require 'nokogiri'
-
-
1
module Rails
-
1
module Dom
-
1
module Testing
-
1
module Assertions
-
1
autoload :DomAssertions, 'rails/dom/testing/assertions/dom_assertions'
-
1
autoload :SelectorAssertions, 'rails/dom/testing/assertions/selector_assertions'
-
1
autoload :TagAssertions, 'rails/dom/testing/assertions/tag_assertions'
-
-
1
extend ActiveSupport::Concern
-
-
1
include DomAssertions
-
1
include SelectorAssertions
-
1
include TagAssertions
-
end
-
end
-
end
-
end
-
1
module Rails
-
1
module Dom
-
1
module Testing
-
1
module Assertions
-
1
module DomAssertions
-
# \Test two HTML strings for equivalency (e.g., equal even when attributes are in another order)
-
#
-
# # assert that the referenced method generates the appropriate HTML string
-
# assert_dom_equal '<a href="http://www.example.com">Apples</a>', link_to("Apples", "http://www.example.com")
-
1
def assert_dom_equal(expected, actual, message = nil)
-
expected_dom, actual_dom = fragment(expected), fragment(actual)
-
message ||= "Expected: #{expected}\nActual: #{actual}"
-
assert compare_doms(expected_dom, actual_dom), message
-
end
-
-
# The negated form of +assert_dom_equal+.
-
#
-
# # assert that the referenced method does not generate the specified HTML string
-
# assert_dom_not_equal '<a href="http://www.example.com">Apples</a>', link_to("Oranges", "http://www.example.com")
-
1
def assert_dom_not_equal(expected, actual, message = nil)
-
expected_dom, actual_dom = fragment(expected), fragment(actual)
-
message ||= "Expected: #{expected}\nActual: #{actual}"
-
assert_not compare_doms(expected_dom, actual_dom), message
-
end
-
-
1
protected
-
-
1
def compare_doms(expected, actual)
-
return false unless expected.children.size == actual.children.size
-
-
expected.children.each_with_index do |child, i|
-
return false unless equal_children?(child, actual.children[i])
-
end
-
-
true
-
end
-
-
1
def equal_children?(child, other_child)
-
return false unless child.type == other_child.type
-
-
if child.element?
-
child.name == other_child.name &&
-
equal_attribute_nodes?(child.attribute_nodes, other_child.attribute_nodes) &&
-
compare_doms(child, other_child)
-
else
-
child.to_s == other_child.to_s
-
end
-
end
-
-
1
def equal_attribute_nodes?(nodes, other_nodes)
-
return false unless nodes.size == other_nodes.size
-
-
nodes = nodes.sort_by(&:name)
-
other_nodes = other_nodes.sort_by(&:name)
-
-
nodes.each_with_index do |attr, i|
-
return false unless equal_attribute?(attr, other_nodes[i])
-
end
-
-
true
-
end
-
-
1
def equal_attribute?(attr, other_attr)
-
attr.name == other_attr.name && attr.value == other_attr.value
-
end
-
-
1
private
-
-
1
def fragment(text)
-
Nokogiri::HTML::DocumentFragment.parse(text)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/deprecation'
-
1
require 'rails/deprecated_sanitizer/html-scanner'
-
-
1
module Rails
-
1
module Dom
-
1
module Testing
-
1
module Assertions
-
# Pair of assertions to testing elements in the HTML output of the response.
-
1
module TagAssertions
-
# Asserts that there is a tag/node/element in the body of the response
-
# that meets all of the given conditions. The +conditions+ parameter must
-
# be a hash of any of the following keys (all are optional):
-
#
-
# * <tt>:tag</tt>: the node type must match the corresponding value
-
# * <tt>:attributes</tt>: a hash. The node's attributes must match the
-
# corresponding values in the hash.
-
# * <tt>:parent</tt>: a hash. The node's parent must match the
-
# corresponding hash.
-
# * <tt>:child</tt>: a hash. At least one of the node's immediate children
-
# must meet the criteria described by the hash.
-
# * <tt>:ancestor</tt>: a hash. At least one of the node's ancestors must
-
# meet the criteria described by the hash.
-
# * <tt>:descendant</tt>: a hash. At least one of the node's descendants
-
# must meet the criteria described by the hash.
-
# * <tt>:sibling</tt>: a hash. At least one of the node's siblings must
-
# meet the criteria described by the hash.
-
# * <tt>:after</tt>: a hash. The node must be after any sibling meeting
-
# the criteria described by the hash, and at least one sibling must match.
-
# * <tt>:before</tt>: a hash. The node must be before any sibling meeting
-
# the criteria described by the hash, and at least one sibling must match.
-
# * <tt>:children</tt>: a hash, for counting children of a node. Accepts
-
# the keys:
-
# * <tt>:count</tt>: either a number or a range which must equal (or
-
# include) the number of children that match.
-
# * <tt>:less_than</tt>: the number of matching children must be less
-
# than this number.
-
# * <tt>:greater_than</tt>: the number of matching children must be
-
# greater than this number.
-
# * <tt>:only</tt>: another hash consisting of the keys to use
-
# to match on the children, and only matching children will be
-
# counted.
-
# * <tt>:content</tt>: the textual content of the node must match the
-
# given value. This will not match HTML tags in the body of a
-
# tag--only text.
-
#
-
# Conditions are matched using the following algorithm:
-
#
-
# * if the condition is a string, it must be a substring of the value.
-
# * if the condition is a regexp, it must match the value.
-
# * if the condition is a number, the value must match number.to_s.
-
# * if the condition is +true+, the value must not be +nil+.
-
# * if the condition is +false+ or +nil+, the value must be +nil+.
-
#
-
# # Assert that there is a "span" tag
-
# assert_tag tag: "span"
-
#
-
# # Assert that there is a "span" tag with id="x"
-
# assert_tag tag: "span", attributes: { id: "x" }
-
#
-
# # Assert that there is a "span" tag using the short-hand
-
# assert_tag :span
-
#
-
# # Assert that there is a "span" tag with id="x" using the short-hand
-
# assert_tag :span, attributes: { id: "x" }
-
#
-
# # Assert that there is a "span" inside of a "div"
-
# assert_tag tag: "span", parent: { tag: "div" }
-
#
-
# # Assert that there is a "span" somewhere inside a table
-
# assert_tag tag: "span", ancestor: { tag: "table" }
-
#
-
# # Assert that there is a "span" with at least one "em" child
-
# assert_tag tag: "span", child: { tag: "em" }
-
#
-
# # Assert that there is a "span" containing a (possibly nested)
-
# # "strong" tag.
-
# assert_tag tag: "span", descendant: { tag: "strong" }
-
#
-
# # Assert that there is a "span" containing between 2 and 4 "em" tags
-
# # as immediate children
-
# assert_tag tag: "span",
-
# children: { count: 2..4, only: { tag: "em" } }
-
#
-
# # Get funky: assert that there is a "div", with an "ul" ancestor
-
# # and an "li" parent (with "class" = "enum"), and containing a
-
# # "span" descendant that contains text matching /hello world/
-
# assert_tag tag: "div",
-
# ancestor: { tag: "ul" },
-
# parent: { tag: "li",
-
# attributes: { class: "enum" } },
-
# descendant: { tag: "span",
-
# child: /hello world/ }
-
#
-
# <b>Please note</b>: +assert_tag+ and +assert_no_tag+ only work
-
# with well-formed XHTML. They recognize a few tags as implicitly self-closing
-
# (like br and hr and such) but will not work correctly with tags
-
# that allow optional closing tags (p, li, td). <em>You must explicitly
-
# close all of your tags to use these assertions.</em>
-
1
def assert_tag(*opts)
-
ActiveSupport::Deprecation.warn("assert_tag is deprecated and will be removed at Rails 5. Use assert_select to get the same feature.")
-
-
opts = opts.size > 1 ? opts.last.merge({ tag: opts.first.to_s }) : opts.first
-
tag = _find_tag(opts)
-
-
assert tag, "expected tag, but no tag found matching #{opts.inspect} in:\n#{@response.body.inspect}"
-
end
-
-
# Identical to +assert_tag+, but asserts that a matching tag does _not_
-
# exist. (See +assert_tag+ for a full discussion of the syntax.)
-
#
-
# # Assert that there is not a "div" containing a "p"
-
# assert_no_tag tag: "div", descendant: { tag: "p" }
-
#
-
# # Assert that an unordered list is empty
-
# assert_no_tag tag: "ul", descendant: { tag: "li" }
-
#
-
# # Assert that there is not a "p" tag with between 1 to 3 "img" tags
-
# # as immediate children
-
# assert_no_tag tag: "p",
-
# children: { count: 1..3, only: { tag: "img" } }
-
1
def assert_no_tag(*opts)
-
ActiveSupport::Deprecation.warn("assert_no_tag is deprecated and will be removed at Rails 5. Use assert_select to get the same feature.")
-
-
opts = opts.size > 1 ? opts.last.merge({ tag: opts.first.to_s }) : opts.first
-
tag = _find_tag(opts)
-
-
assert !tag, "expected no tag, but found tag matching #{opts.inspect} in:\n#{@response.body.inspect}"
-
end
-
-
1
def find_tag(conditions)
-
ActiveSupport::Deprecation.warn("find_tag is deprecated and will be removed at Rails 5 without replacement.")
-
-
_find_tag(conditions)
-
end
-
-
1
def find_all_tag(conditions)
-
ActiveSupport::Deprecation.warn("find_all_tag is deprecated and will be removed at Rails 5 without replacement. Use assert_select to get the same feature.")
-
-
html_scanner_document.find_all(conditions)
-
end
-
-
1
private
-
1
def _find_tag(conditions)
-
html_scanner_document.find(conditions)
-
end
-
-
1
def html_scanner_document
-
xml = @response.content_type =~ /xml$/
-
@html_scanner_document ||= HTML::Document.new(@response.body, false, xml)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'active_support/backtrace_cleaner'
-
-
1
module Rails
-
1
class BacktraceCleaner < ActiveSupport::BacktraceCleaner
-
1
APP_DIRS_PATTERN = /^\/?(app|config|lib|test)/
-
1
RENDER_TEMPLATE_PATTERN = /:in `_render_template_\w*'/
-
1
EMPTY_STRING = ''.freeze
-
1
SLASH = '/'.freeze
-
1
DOT_SLASH = './'.freeze
-
-
1
def initialize
-
1
super
-
1
@root = "#{Rails.root}/".freeze
-
1
add_filter { |line| line.sub(@root, EMPTY_STRING) }
-
1
add_filter { |line| line.sub(RENDER_TEMPLATE_PATTERN, EMPTY_STRING) }
-
1
add_filter { |line| line.sub(DOT_SLASH, SLASH) } # for tests
-
-
1
add_gem_filters
-
1
add_silencer { |line| line !~ APP_DIRS_PATTERN }
-
end
-
-
1
private
-
1
def add_gem_filters
-
3
gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) }
-
1
return if gems_paths.empty?
-
-
1
gems_regexp = %r{(#{gems_paths.join('|')})/gems/([^/]+)-([\w.]+)/(.*)}
-
1
gems_result = '\2 (\3) \4'.freeze
-
1
add_filter { |line| line.sub(gems_regexp, gems_result) }
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/helpers"
-
1
require 'stringio'
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# RSpec's built-in formatters are all subclasses of
-
# RSpec::Core::Formatters::BaseTextFormatter.
-
#
-
# @see RSpec::Core::Formatters::BaseTextFormatter
-
# @see RSpec::Core::Reporter
-
# @see RSpec::Core::Formatters::Protocol
-
1
class BaseFormatter
-
# All formatters inheriting from this formatter will receive these
-
# notifications.
-
1
Formatters.register self, :start, :example_group_started, :close
-
1
attr_accessor :example_group
-
1
attr_reader :output
-
-
# @api public
-
# @param output [IO] the formatter output
-
# @see RSpec::Core::Formatters::Protocol#initialize
-
1
def initialize(output)
-
1
@output = output || StringIO.new
-
1
@example_group = nil
-
end
-
-
# @api public
-
#
-
# @param notification [StartNotification]
-
# @see RSpec::Core::Formatters::Protocol#start
-
1
def start(notification)
-
1
start_sync_output
-
1
@example_count = notification.count
-
end
-
-
# @api public
-
#
-
# @param notification [GroupNotification] containing example_group
-
# subclass of `RSpec::Core::ExampleGroup`
-
# @see RSpec::Core::Formatters::Protocol#example_group_started
-
1
def example_group_started(notification)
-
2
@example_group = notification.group
-
end
-
-
# @api public
-
#
-
# @param _notification [NullNotification] (Ignored)
-
# @see RSpec::Core::Formatters::Protocol#close
-
1
def close(_notification)
-
restore_sync_output
-
end
-
-
1
private
-
-
1
def start_sync_output
-
1
@old_sync, output.sync = output.sync, true if output_supports_sync
-
end
-
-
1
def restore_sync_output
-
output.sync = @old_sync if output_supports_sync && !output.closed?
-
end
-
-
1
def output_supports_sync
-
1
output.respond_to?(:sync=)
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/base_formatter"
-
1
RSpec::Support.require_rspec_core "formatters/console_codes"
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# Base for all of RSpec's built-in formatters. See
-
# RSpec::Core::Formatters::BaseFormatter to learn more about all of the
-
# methods called by the reporter.
-
#
-
# @see RSpec::Core::Formatters::BaseFormatter
-
# @see RSpec::Core::Reporter
-
1
class BaseTextFormatter < BaseFormatter
-
1
Formatters.register self,
-
:message, :dump_summary, :dump_failures, :dump_pending, :seed
-
-
# @api public
-
#
-
# Used by the reporter to send messages to the output stream.
-
#
-
# @param notification [MessageNotification] containing message
-
1
def message(notification)
-
output.puts notification.message
-
end
-
-
# @api public
-
#
-
# Dumps detailed information about each example failure.
-
#
-
# @param notification [NullNotification]
-
1
def dump_failures(notification)
-
1
return if notification.failure_notifications.empty?
-
output.puts notification.fully_formatted_failed_examples
-
end
-
-
# @api public
-
#
-
# This method is invoked after the dumping of examples and failures.
-
# Each parameter is assigned to a corresponding attribute.
-
#
-
# @param summary [SummaryNotification] containing duration,
-
# example_count, failure_count and pending_count
-
1
def dump_summary(summary)
-
1
output.puts summary.fully_formatted
-
end
-
-
# @private
-
1
def dump_pending(notification)
-
1
return if notification.pending_examples.empty?
-
output.puts notification.fully_formatted_pending_examples
-
end
-
-
# @private
-
1
def seed(notification)
-
2
return unless notification.seed_used?
-
output.puts notification.fully_formatted
-
end
-
-
# @api public
-
#
-
# Invoked at the very end, `close` allows the formatter to clean
-
# up resources, e.g. open streams, etc.
-
#
-
# @param _notification [NullNotification] (Ignored)
-
1
def close(_notification)
-
1
return unless IO === output
-
1
return if output.closed?
-
-
1
output.puts
-
-
1
output.flush
-
1
output.close unless output == $stdout
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# ConsoleCodes provides helpers for formatting console output
-
# with ANSI codes, e.g. color's and bold.
-
1
module ConsoleCodes
-
# @private
-
1
VT100_CODES =
-
{
-
:black => 30,
-
:red => 31,
-
:green => 32,
-
:yellow => 33,
-
:blue => 34,
-
:magenta => 35,
-
:cyan => 36,
-
:white => 37,
-
:bold => 1,
-
}
-
# @private
-
1
VT100_CODE_VALUES = VT100_CODES.invert
-
-
1
module_function
-
-
# @private
-
1
CONFIG_COLORS_TO_METHODS = Configuration.instance_methods.grep(/_color\z/).inject({}) do |hash, method|
-
6
hash[method.to_s.sub(/_color\z/, '').to_sym] = method
-
6
hash
-
end
-
-
# Fetches the correct code for the supplied symbol, or checks
-
# that a code is valid. Defaults to white (37).
-
#
-
# @param code_or_symbol [Symbol, Fixnum] Symbol or code to check
-
# @return [Fixnum] a console code
-
1
def console_code_for(code_or_symbol)
-
if (config_method = CONFIG_COLORS_TO_METHODS[code_or_symbol])
-
console_code_for RSpec.configuration.__send__(config_method)
-
elsif VT100_CODE_VALUES.key?(code_or_symbol)
-
code_or_symbol
-
else
-
VT100_CODES.fetch(code_or_symbol) do
-
console_code_for(:white)
-
end
-
end
-
end
-
-
# Wraps a piece of text in ANSI codes with the supplied code. Will
-
# only apply the control code if `RSpec.configuration.color_enabled?`
-
# returns true.
-
#
-
# @param text [String] the text to wrap
-
# @param code_or_symbol [Symbol, Fixnum] the desired control code
-
# @return [String] the wrapped text
-
1
def wrap(text, code_or_symbol)
-
8
if RSpec.configuration.color_enabled?
-
"\e[#{console_code_for(code_or_symbol)}m#{text}\e[0m"
-
else
-
8
text
-
end
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_core "formatters/base_text_formatter"
-
-
1
module RSpec
-
1
module Core
-
1
module Formatters
-
# @private
-
1
class ProgressFormatter < BaseTextFormatter
-
1
Formatters.register self, :example_passed, :example_pending, :example_failed, :start_dump
-
-
1
def example_passed(_notification)
-
7
output.print ConsoleCodes.wrap('.', :success)
-
end
-
-
1
def example_pending(_notification)
-
output.print ConsoleCodes.wrap('*', :pending)
-
end
-
-
1
def example_failed(_notification)
-
output.print ConsoleCodes.wrap('F', :failure)
-
end
-
-
1
def start_dump(_notification)
-
1
output.puts
-
end
-
end
-
end
-
end
-
end
-
1
require 'rspec/mocks'
-
-
1
module RSpec
-
1
module Core
-
1
module MockingAdapters
-
# @private
-
1
module RSpec
-
1
include ::RSpec::Mocks::ExampleMethods
-
-
1
def self.framework_name
-
1
:rspec
-
end
-
-
1
def self.configuration
-
1
::RSpec::Mocks.configuration
-
end
-
-
1
def setup_mocks_for_rspec
-
7
::RSpec::Mocks.setup
-
end
-
-
1
def verify_mocks_for_rspec
-
7
::RSpec::Mocks.verify
-
end
-
-
1
def teardown_mocks_for_rspec
-
7
::RSpec::Mocks.teardown
-
end
-
end
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support "caller_filter"
-
1
RSpec::Support.require_rspec_support "warnings"
-
1
RSpec::Support.require_rspec_support "object_formatter"
-
-
1
require 'rspec/matchers'
-
-
7
RSpec::Support.define_optimized_require_for_rspec(:expectations) { |f| require_relative(f) }
-
-
%w[
-
expectation_target
-
configuration
-
fail_with
-
handler
-
version
-
6
].each { |file| RSpec::Support.require_rspec_expectations(file) }
-
-
1
module RSpec
-
# RSpec::Expectations provides a simple, readable API to express
-
# the expected outcomes in a code example. To express an expected
-
# outcome, wrap an object or block in `expect`, call `to` or `to_not`
-
# (aliased as `not_to`) and pass it a matcher object:
-
#
-
# expect(order.total).to eq(Money.new(5.55, :USD))
-
# expect(list).to include(user)
-
# expect(message).not_to match(/foo/)
-
# expect { do_something }.to raise_error
-
#
-
# The last form (the block form) is needed to match against ruby constructs
-
# that are not objects, but can only be observed when executing a block
-
# of code. This includes raising errors, throwing symbols, yielding,
-
# and changing values.
-
#
-
# When `expect(...).to` is invoked with a matcher, it turns around
-
# and calls `matcher.matches?(<object wrapped by expect>)`. For example,
-
# in the expression:
-
#
-
# expect(order.total).to eq(Money.new(5.55, :USD))
-
#
-
# ...`eq(Money.new(5.55, :USD))` returns a matcher object, and it results
-
# in the equivalent of `eq.matches?(order.total)`. If `matches?` returns
-
# `true`, the expectation is met and execution continues. If `false`, then
-
# the spec fails with the message returned by `eq.failure_message`.
-
#
-
# Given the expression:
-
#
-
# expect(order.entries).not_to include(entry)
-
#
-
# ...the `not_to` method (also available as `to_not`) invokes the equivalent of
-
# `include.matches?(order.entries)`, but it interprets `false` as success, and
-
# `true` as a failure, using the message generated by
-
# `include.failure_message_when_negated`.
-
#
-
# rspec-expectations ships with a standard set of useful matchers, and writing
-
# your own matchers is quite simple.
-
#
-
# See [RSpec::Matchers](../RSpec/Matchers) for more information about the
-
# built-in matchers that ship with rspec-expectations, and how to write your
-
# own custom matchers.
-
1
module Expectations
-
# Exception raised when an expectation fails.
-
#
-
# @note We subclass Exception so that in a stub implementation if
-
# the user sets an expectation, it can't be caught in their
-
# code by a bare `rescue`.
-
# @api public
-
1
class ExpectationNotMetError < Exception
-
end
-
-
# Exception raised from `aggregate_failures` when multiple expectations fail.
-
#
-
# @note The constant is defined here but the extensive logic of this class
-
# is lazily defined when `FailureAggregator` is autoloaded, since we do
-
# not need to waste time defining that functionality unless
-
# `aggregate_failures` is used.
-
1
class MultipleExpectationsNotMetError < ExpectationNotMetError
-
end
-
-
1
autoload :FailureAggregator, "rspec/expectations/failure_aggregator"
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# Wraps the target of an expectation.
-
#
-
# @example
-
# expect(something) # => ExpectationTarget wrapping something
-
# expect { do_something } # => ExpectationTarget wrapping the block
-
#
-
# # used with `to`
-
# expect(actual).to eq(3)
-
#
-
# # with `not_to`
-
# expect(actual).not_to eq(3)
-
#
-
# @note `ExpectationTarget` is not intended to be instantiated
-
# directly by users. Use `expect` instead.
-
1
class ExpectationTarget
-
# @private
-
# Used as a sentinel value to be able to tell when the user
-
# did not pass an argument. We can't use `nil` for that because
-
# `nil` is a valid value to pass.
-
1
UndefinedValue = Module.new
-
-
# @api private
-
1
def initialize(value)
-
24
@target = value
-
end
-
-
# @private
-
1
def self.for(value, block)
-
24
if UndefinedValue.equal?(value)
-
unless block
-
raise ArgumentError, "You must pass either an argument or a block to `expect`."
-
end
-
BlockExpectationTarget.new(block)
-
24
elsif block
-
raise ArgumentError, "You cannot pass both an argument and a block to `expect`."
-
else
-
24
new(value)
-
end
-
end
-
-
# Runs the given expectation, passing if `matcher` returns true.
-
# @example
-
# expect(value).to eq(5)
-
# expect { perform }.to raise_error
-
# @param [Matcher]
-
# matcher
-
# @param [String or Proc] message optional message to display when the expectation fails
-
# @return [Boolean] true if the expectation succeeds (else raises)
-
# @see RSpec::Matchers
-
1
def to(matcher=nil, message=nil, &block)
-
23
prevent_operator_matchers(:to) unless matcher
-
23
RSpec::Expectations::PositiveExpectationHandler.handle_matcher(@target, matcher, message, &block)
-
end
-
-
# Runs the given expectation, passing if `matcher` returns false.
-
# @example
-
# expect(value).not_to eq(5)
-
# @param [Matcher]
-
# matcher
-
# @param [String or Proc] message optional message to display when the expectation fails
-
# @return [Boolean] false if the negative expectation succeeds (else raises)
-
# @see RSpec::Matchers
-
1
def not_to(matcher=nil, message=nil, &block)
-
prevent_operator_matchers(:not_to) unless matcher
-
RSpec::Expectations::NegativeExpectationHandler.handle_matcher(@target, matcher, message, &block)
-
end
-
1
alias to_not not_to
-
-
1
private
-
-
1
def prevent_operator_matchers(verb)
-
raise ArgumentError, "The expect syntax does not support operator matchers, " \
-
"so you must pass a matcher to `##{verb}`."
-
end
-
end
-
-
# @private
-
# Validates the provided matcher to ensure it supports block
-
# expectations, in order to avoid user confusion when they
-
# use a block thinking the expectation will be on the return
-
# value of the block rather than the block itself.
-
1
class BlockExpectationTarget < ExpectationTarget
-
1
def to(matcher, message=nil, &block)
-
enforce_block_expectation(matcher)
-
super
-
end
-
-
1
def not_to(matcher, message=nil, &block)
-
enforce_block_expectation(matcher)
-
super
-
end
-
1
alias to_not not_to
-
-
1
private
-
-
1
def enforce_block_expectation(matcher)
-
return if supports_block_expectations?(matcher)
-
-
raise ExpectationNotMetError, "You must pass an argument rather than a block to use the provided " \
-
"matcher (#{RSpec::Support::ObjectFormatter.format(matcher)}), or the matcher must implement " \
-
"`supports_block_expectations?`."
-
end
-
-
1
def supports_block_expectations?(matcher)
-
matcher.supports_block_expectations?
-
rescue NoMethodError
-
false
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
1
class << self
-
# @private
-
1
def differ
-
RSpec::Support::Differ.new(
-
:object_preparer => lambda { |object| RSpec::Matchers::Composable.surface_descriptions_in(object) },
-
:color => RSpec::Matchers.configuration.color?
-
)
-
end
-
-
# Raises an RSpec::Expectations::ExpectationNotMetError with message.
-
# @param [String] message
-
# @param [Object] expected
-
# @param [Object] actual
-
#
-
# Adds a diff to the failure message when `expected` and `actual` are
-
# both present.
-
1
def fail_with(message, expected=nil, actual=nil)
-
unless message
-
raise ArgumentError, "Failure message is nil. Does your matcher define the " \
-
"appropriate failure_message[_when_negated] method to return a string?"
-
end
-
-
message = ::RSpec::Matchers::ExpectedsForMultipleDiffs.from(expected).message_with_diff(message, differ, actual)
-
-
RSpec::Support.notify_failure(RSpec::Expectations::ExpectationNotMetError.new message)
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# @private
-
1
module ExpectationHelper
-
1
def self.check_message(msg)
-
23
unless msg.nil? || msg.respond_to?(:to_str) || msg.respond_to?(:call)
-
::Kernel.warn [
-
"WARNING: ignoring the provided expectation message argument (",
-
msg.inspect,
-
") since it is not a string or a proc."
-
].join
-
end
-
end
-
-
# Returns an RSpec-3+ compatible matcher, wrapping a legacy one
-
# in an adapter if necessary.
-
#
-
# @private
-
1
def self.modern_matcher_from(matcher)
-
LegacyMatcherAdapter::RSpec2.wrap(matcher) ||
-
23
LegacyMatcherAdapter::RSpec1.wrap(matcher) || matcher
-
end
-
-
1
def self.with_matcher(handler, matcher, message)
-
23
check_message(message)
-
23
matcher = modern_matcher_from(matcher)
-
23
yield matcher
-
ensure
-
23
::RSpec::Matchers.last_expectation_handler = handler
-
23
::RSpec::Matchers.last_matcher = matcher
-
end
-
-
1
def self.handle_failure(matcher, message, failure_message_method)
-
message = message.call if message.respond_to?(:call)
-
message ||= matcher.__send__(failure_message_method)
-
-
if matcher.respond_to?(:diffable?) && matcher.diffable?
-
::RSpec::Expectations.fail_with message, matcher.expected, matcher.actual
-
else
-
::RSpec::Expectations.fail_with message
-
end
-
end
-
end
-
-
# @private
-
1
class PositiveExpectationHandler
-
1
def self.handle_matcher(actual, initial_matcher, message=nil, &block)
-
23
ExpectationHelper.with_matcher(self, initial_matcher, message) do |matcher|
-
23
return ::RSpec::Matchers::BuiltIn::PositiveOperatorMatcher.new(actual) unless initial_matcher
-
23
matcher.matches?(actual, &block) || ExpectationHelper.handle_failure(matcher, message, :failure_message)
-
end
-
end
-
-
1
def self.verb
-
"should"
-
end
-
-
1
def self.should_method
-
:should
-
end
-
-
1
def self.opposite_should_method
-
:should_not
-
end
-
end
-
-
# @private
-
1
class NegativeExpectationHandler
-
1
def self.handle_matcher(actual, initial_matcher, message=nil, &block)
-
ExpectationHelper.with_matcher(self, initial_matcher, message) do |matcher|
-
return ::RSpec::Matchers::BuiltIn::NegativeOperatorMatcher.new(actual) unless initial_matcher
-
!(does_not_match?(matcher, actual, &block) || ExpectationHelper.handle_failure(matcher, message, :failure_message_when_negated))
-
end
-
end
-
-
1
def self.does_not_match?(matcher, actual, &block)
-
if matcher.respond_to?(:does_not_match?)
-
matcher.does_not_match?(actual, &block)
-
else
-
!matcher.matches?(actual, &block)
-
end
-
end
-
-
1
def self.verb
-
"should not"
-
end
-
-
1
def self.should_method
-
:should_not
-
end
-
-
1
def self.opposite_should_method
-
:should
-
end
-
end
-
-
# Wraps a matcher written against one of the legacy protocols in
-
# order to present the current protocol.
-
#
-
# @private
-
1
class LegacyMatcherAdapter < Matchers::MatcherDelegator
-
1
def initialize(matcher)
-
super
-
::RSpec.warn_deprecation(<<-EOS.gsub(/^\s+\|/, ''), :type => "legacy_matcher")
-
|#{matcher.class.name || matcher.inspect} implements a legacy RSpec matcher
-
|protocol. For the current protocol you should expose the failure messages
-
|via the `failure_message` and `failure_message_when_negated` methods.
-
|(Used from #{CallerFilter.first_non_rspec_line})
-
EOS
-
end
-
-
1
def self.wrap(matcher)
-
46
new(matcher) if interface_matches?(matcher)
-
end
-
-
# Starting in RSpec 1.2 (and continuing through all 2.x releases),
-
# the failure message protocol was:
-
# * `failure_message_for_should`
-
# * `failure_message_for_should_not`
-
# @private
-
1
class RSpec2 < self
-
1
def failure_message
-
base_matcher.failure_message_for_should
-
end
-
-
1
def failure_message_when_negated
-
base_matcher.failure_message_for_should_not
-
end
-
-
1
def self.interface_matches?(matcher)
-
(
-
!matcher.respond_to?(:failure_message) &&
-
23
matcher.respond_to?(:failure_message_for_should)
-
) || (
-
!matcher.respond_to?(:failure_message_when_negated) &&
-
23
matcher.respond_to?(:failure_message_for_should_not)
-
23
)
-
end
-
end
-
-
# Before RSpec 1.2, the failure message protocol was:
-
# * `failure_message`
-
# * `negative_failure_message`
-
# @private
-
1
class RSpec1 < self
-
1
def failure_message
-
base_matcher.failure_message
-
end
-
-
1
def failure_message_when_negated
-
base_matcher.negative_failure_message
-
end
-
-
# Note: `failure_message` is part of the RSpec 3 protocol
-
# (paired with `failure_message_when_negated`), so we don't check
-
# for `failure_message` here.
-
1
def self.interface_matches?(matcher)
-
!matcher.respond_to?(:failure_message_when_negated) &&
-
23
matcher.respond_to?(:negative_failure_message)
-
end
-
end
-
end
-
-
# RSpec 3.0 was released with the class name misspelled. For SemVer compatibility,
-
# we will provide this misspelled alias until 4.0.
-
# @deprecated Use LegacyMatcherAdapter instead.
-
# @private
-
1
LegacyMacherAdapter = LegacyMatcherAdapter
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# @api private
-
# Provides methods for enabling and disabling the available
-
# syntaxes provided by rspec-expectations.
-
1
module Syntax
-
1
module_function
-
-
# @api private
-
# Determines where we add `should` and `should_not`.
-
1
def default_should_host
-
4
@default_should_host ||= ::Object.ancestors.last
-
end
-
-
# @api private
-
# Instructs rspec-expectations to warn on first usage of `should` or `should_not`.
-
# Enabled by default. This is largely here to facilitate testing.
-
1
def warn_about_should!
-
1
@warn_about_should = true
-
end
-
-
# @api private
-
# Generates a deprecation warning for the given method if no warning
-
# has already been issued.
-
1
def warn_about_should_unless_configured(method_name)
-
return unless @warn_about_should
-
-
RSpec.deprecate(
-
"Using `#{method_name}` from rspec-expectations' old `:should` syntax without explicitly enabling the syntax",
-
:replacement => "the new `:expect` syntax or explicitly enable `:should` with `config.expect_with(:rspec) { |c| c.syntax = :should }`"
-
)
-
-
@warn_about_should = false
-
end
-
-
# @api private
-
# Enables the `should` syntax.
-
1
def enable_should(syntax_host=default_should_host)
-
2
@warn_about_should = false if syntax_host == default_should_host
-
2
return if should_enabled?(syntax_host)
-
-
1
syntax_host.module_exec do
-
1
def should(matcher=nil, message=nil, &block)
-
::RSpec::Expectations::Syntax.warn_about_should_unless_configured(__method__)
-
::RSpec::Expectations::PositiveExpectationHandler.handle_matcher(self, matcher, message, &block)
-
end
-
-
1
def should_not(matcher=nil, message=nil, &block)
-
::RSpec::Expectations::Syntax.warn_about_should_unless_configured(__method__)
-
::RSpec::Expectations::NegativeExpectationHandler.handle_matcher(self, matcher, message, &block)
-
end
-
end
-
end
-
-
# @api private
-
# Disables the `should` syntax.
-
1
def disable_should(syntax_host=default_should_host)
-
return unless should_enabled?(syntax_host)
-
-
syntax_host.module_exec do
-
undef should
-
undef should_not
-
end
-
end
-
-
# @api private
-
# Enables the `expect` syntax.
-
1
def enable_expect(syntax_host=::RSpec::Matchers)
-
1
return if expect_enabled?(syntax_host)
-
-
1
syntax_host.module_exec do
-
1
def expect(value=::RSpec::Expectations::ExpectationTarget::UndefinedValue, &block)
-
24
::RSpec::Expectations::ExpectationTarget.for(value, block)
-
end
-
end
-
end
-
-
# @api private
-
# Disables the `expect` syntax.
-
1
def disable_expect(syntax_host=::RSpec::Matchers)
-
return unless expect_enabled?(syntax_host)
-
-
syntax_host.module_exec do
-
undef expect
-
end
-
end
-
-
# @api private
-
# Indicates whether or not the `should` syntax is enabled.
-
1
def should_enabled?(syntax_host=default_should_host)
-
3
syntax_host.method_defined?(:should)
-
end
-
-
# @api private
-
# Indicates whether or not the `expect` syntax is enabled.
-
1
def expect_enabled?(syntax_host=::RSpec::Matchers)
-
2
syntax_host.method_defined?(:expect)
-
end
-
end
-
end
-
end
-
-
1
if defined?(BasicObject)
-
# The legacy `:should` syntax adds the following methods directly to
-
# `BasicObject` so that they are available off of any object. Note, however,
-
# that this syntax does not always play nice with delegate/proxy objects.
-
# We recommend you use the non-monkeypatching `:expect` syntax instead.
-
1
class BasicObject
-
# @method should
-
# Passes if `matcher` returns true. Available on every `Object`.
-
# @example
-
# actual.should eq expected
-
# actual.should match /expression/
-
# @param [Matcher]
-
# matcher
-
# @param [String] message optional message to display when the expectation fails
-
# @return [Boolean] true if the expectation succeeds (else raises)
-
# @note This is only available when you have enabled the `:should` syntax.
-
# @see RSpec::Matchers
-
-
# @method should_not
-
# Passes if `matcher` returns false. Available on every `Object`.
-
# @example
-
# actual.should_not eq expected
-
# @param [Matcher]
-
# matcher
-
# @param [String] message optional message to display when the expectation fails
-
# @return [Boolean] false if the negative expectation succeeds (else raises)
-
# @note This is only available when you have enabled the `:should` syntax.
-
# @see RSpec::Matchers
-
end
-
end
-
1
module RSpec
-
1
module Expectations
-
# @private
-
1
module Version
-
1
STRING = '3.3.1'
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support 'matcher_definition'
-
10
RSpec::Support.define_optimized_require_for_rspec(:matchers) { |f| require_relative(f) }
-
-
%w[
-
english_phrasing
-
composable
-
built_in
-
generated_descriptions
-
dsl
-
matcher_delegator
-
aliased_matcher
-
expecteds_for_multiple_diffs
-
9
].each { |file| RSpec::Support.require_rspec_matchers(file) }
-
-
# RSpec's top level namespace. All of rspec-expectations is contained
-
# in the `RSpec::Expectations` and `RSpec::Matchers` namespaces.
-
1
module RSpec
-
# RSpec::Matchers provides a number of useful matchers we use to define
-
# expectations. Any object that implements the [matcher protocol](Matchers/MatcherProtocol)
-
# can be used as a matcher.
-
#
-
# ## Predicates
-
#
-
# In addition to matchers that are defined explicitly, RSpec will create
-
# custom matchers on the fly for any arbitrary predicate, giving your specs a
-
# much more natural language feel.
-
#
-
# A Ruby predicate is a method that ends with a "?" and returns true or false.
-
# Common examples are `empty?`, `nil?`, and `instance_of?`.
-
#
-
# All you need to do is write `expect(..).to be_` followed by the predicate
-
# without the question mark, and RSpec will figure it out from there.
-
# For example:
-
#
-
# expect([]).to be_empty # => [].empty?() | passes
-
# expect([]).not_to be_empty # => [].empty?() | fails
-
#
-
# In addtion to prefixing the predicate matchers with "be_", you can also use "be_a_"
-
# and "be_an_", making your specs read much more naturally:
-
#
-
# expect("a string").to be_an_instance_of(String) # =>"a string".instance_of?(String) # passes
-
#
-
# expect(3).to be_a_kind_of(Fixnum) # => 3.kind_of?(Numeric) | passes
-
# expect(3).to be_a_kind_of(Numeric) # => 3.kind_of?(Numeric) | passes
-
# expect(3).to be_an_instance_of(Fixnum) # => 3.instance_of?(Fixnum) | passes
-
# expect(3).not_to be_an_instance_of(Numeric) # => 3.instance_of?(Numeric) | fails
-
#
-
# RSpec will also create custom matchers for predicates like `has_key?`. To
-
# use this feature, just state that the object should have_key(:key) and RSpec will
-
# call has_key?(:key) on the target. For example:
-
#
-
# expect(:a => "A").to have_key(:a)
-
# expect(:a => "A").to have_key(:b) # fails
-
#
-
# You can use this feature to invoke any predicate that begins with "has_", whether it is
-
# part of the Ruby libraries (like `Hash#has_key?`) or a method you wrote on your own class.
-
#
-
# Note that RSpec does not provide composable aliases for these dynamic predicate
-
# matchers. You can easily define your own aliases, though:
-
#
-
# RSpec::Matchers.alias_matcher :a_user_who_is_an_admin, :be_an_admin
-
# expect(user_list).to include(a_user_who_is_an_admin)
-
#
-
# ## Custom Matchers
-
#
-
# When you find that none of the stock matchers provide a natural feeling
-
# expectation, you can very easily write your own using RSpec's matcher DSL
-
# or writing one from scratch.
-
#
-
# ### Matcher DSL
-
#
-
# Imagine that you are writing a game in which players can be in various
-
# zones on a virtual board. To specify that bob should be in zone 4, you
-
# could say:
-
#
-
# expect(bob.current_zone).to eql(Zone.new("4"))
-
#
-
# But you might find it more expressive to say:
-
#
-
# expect(bob).to be_in_zone("4")
-
#
-
# and/or
-
#
-
# expect(bob).not_to be_in_zone("3")
-
#
-
# You can create such a matcher like so:
-
#
-
# RSpec::Matchers.define :be_in_zone do |zone|
-
# match do |player|
-
# player.in_zone?(zone)
-
# end
-
# end
-
#
-
# This will generate a <tt>be_in_zone</tt> method that returns a matcher
-
# with logical default messages for failures. You can override the failure
-
# messages and the generated description as follows:
-
#
-
# RSpec::Matchers.define :be_in_zone do |zone|
-
# match do |player|
-
# player.in_zone?(zone)
-
# end
-
#
-
# failure_message do |player|
-
# # generate and return the appropriate string.
-
# end
-
#
-
# failure_message_when_negated do |player|
-
# # generate and return the appropriate string.
-
# end
-
#
-
# description do
-
# # generate and return the appropriate string.
-
# end
-
# end
-
#
-
# Each of the message-generation methods has access to the block arguments
-
# passed to the <tt>create</tt> method (in this case, <tt>zone</tt>). The
-
# failure message methods (<tt>failure_message</tt> and
-
# <tt>failure_message_when_negated</tt>) are passed the actual value (the
-
# receiver of <tt>expect(..)</tt> or <tt>expect(..).not_to</tt>).
-
#
-
# ### Custom Matcher from scratch
-
#
-
# You could also write a custom matcher from scratch, as follows:
-
#
-
# class BeInZone
-
# def initialize(expected)
-
# @expected = expected
-
# end
-
#
-
# def matches?(target)
-
# @target = target
-
# @target.current_zone.eql?(Zone.new(@expected))
-
# end
-
#
-
# def failure_message
-
# "expected #{@target.inspect} to be in Zone #{@expected}"
-
# end
-
#
-
# def failure_message_when_negated
-
# "expected #{@target.inspect} not to be in Zone #{@expected}"
-
# end
-
# end
-
#
-
# ... and a method like this:
-
#
-
# def be_in_zone(expected)
-
# BeInZone.new(expected)
-
# end
-
#
-
# And then expose the method to your specs. This is normally done
-
# by including the method and the class in a module, which is then
-
# included in your spec:
-
#
-
# module CustomGameMatchers
-
# class BeInZone
-
# # ...
-
# end
-
#
-
# def be_in_zone(expected)
-
# # ...
-
# end
-
# end
-
#
-
# describe "Player behaviour" do
-
# include CustomGameMatchers
-
# # ...
-
# end
-
#
-
# or you can include in globally in a spec_helper.rb file <tt>require</tt>d
-
# from your spec file(s):
-
#
-
# RSpec::configure do |config|
-
# config.include(CustomGameMatchers)
-
# end
-
#
-
# ### Making custom matchers composable
-
#
-
# RSpec's built-in matchers are designed to be composed, in expressions like:
-
#
-
# expect(["barn", 2.45]).to contain_exactly(
-
# a_value_within(0.1).of(2.5),
-
# a_string_starting_with("bar")
-
# )
-
#
-
# Custom matchers can easily participate in composed matcher expressions like these.
-
# Include {RSpec::Matchers::Composable} in your custom matcher to make it support
-
# being composed (matchers defined using the DSL have this included automatically).
-
# Within your matcher's `matches?` method (or the `match` block, if using the DSL),
-
# use `values_match?(expected, actual)` rather than `expected == actual`.
-
# Under the covers, `values_match?` is able to match arbitrary
-
# nested data structures containing a mix of both matchers and non-matcher objects.
-
# It uses `===` and `==` to perform the matching, considering the values to
-
# match if either returns `true`. The `Composable` mixin also provides some helper
-
# methods for surfacing the matcher descriptions within your matcher's description
-
# or failure messages.
-
#
-
# RSpec's built-in matchers each have a number of aliases that rephrase the matcher
-
# from a verb phrase (such as `be_within`) to a noun phrase (such as `a_value_within`),
-
# which reads better when the matcher is passed as an argument in a composed matcher
-
# expressions, and also uses the noun-phrase wording in the matcher's `description`,
-
# for readable failure messages. You can alias your custom matchers in similar fashion
-
# using {RSpec::Matchers.alias_matcher}.
-
1
module Matchers
-
# @method expect
-
# Supports `expect(actual).to matcher` syntax by wrapping `actual` in an
-
# `ExpectationTarget`.
-
# @example
-
# expect(actual).to eq(expected)
-
# expect(actual).not_to eq(expected)
-
# @return [ExpectationTarget]
-
# @see ExpectationTarget#to
-
# @see ExpectationTarget#not_to
-
-
# Defines a matcher alias. The returned matcher's `description` will be overriden
-
# to reflect the phrasing of the new name, which will be used in failure messages
-
# when passed as an argument to another matcher in a composed matcher expression.
-
#
-
# @param new_name [Symbol] the new name for the matcher
-
# @param old_name [Symbol] the original name for the matcher
-
# @param options [Hash] options for the aliased matcher
-
# @option options [Class] :klass the ruby class to use as the decorator. (Not normally used).
-
# @yield [String] optional block that, when given, is used to define the overriden
-
# logic. The yielded arg is the original description or failure message. If no
-
# block is provided, a default override is used based on the old and new names.
-
#
-
# @example
-
# RSpec::Matchers.alias_matcher :a_list_that_sums_to, :sum_to
-
# sum_to(3).description # => "sum to 3"
-
# a_list_that_sums_to(3).description # => "a list that sums to 3"
-
#
-
# @example
-
# RSpec::Matchers.alias_matcher :a_list_sorted_by, :be_sorted_by do |description|
-
# description.sub("be sorted by", "a list sorted by")
-
# end
-
#
-
# be_sorted_by(:age).description # => "be sorted by age"
-
# a_list_sorted_by(:age).description # => "a list sorted by age"
-
#
-
# @!macro [attach] alias_matcher
-
# @!parse
-
# alias $1 $2
-
1
def self.alias_matcher(new_name, old_name, options={}, &description_override)
-
description_override ||= lambda do |old_desc|
-
old_desc.gsub(EnglishPhrasing.split_words(old_name), EnglishPhrasing.split_words(new_name))
-
57
end
-
113
klass = options.fetch(:klass) { AliasedMatcher }
-
-
57
define_method(new_name) do |*args, &block|
-
matcher = __send__(old_name, *args, &block)
-
klass.new(matcher, description_override)
-
end
-
end
-
-
# Defines a negated matcher. The returned matcher's `description` and `failure_message`
-
# will be overriden to reflect the phrasing of the new name, and the match logic will
-
# be based on the original matcher but negated.
-
#
-
# @param negated_name [Symbol] the name for the negated matcher
-
# @param base_name [Symbol] the name of the original matcher that will be negated
-
# @yield [String] optional block that, when given, is used to define the overriden
-
# logic. The yielded arg is the original description or failure message. If no
-
# block is provided, a default override is used based on the old and new names.
-
#
-
# @example
-
# RSpec::Matchers.define_negated_matcher :exclude, :include
-
# include(1, 2).description # => "include 1 and 2"
-
# exclude(1, 2).description # => "exclude 1 and 2"
-
#
-
# @note While the most obvious negated form may be to add a `not_` prefix,
-
# the failure messages you get with that form can be confusing (e.g.
-
# "expected [actual] to not [verb], but did not"). We've found it works
-
# best to find a more positive name for the negated form, such as
-
# `avoid_changing` rather than `not_change`.
-
1
def self.define_negated_matcher(negated_name, base_name, &description_override)
-
alias_matcher(negated_name, base_name, :klass => AliasedNegatedMatcher, &description_override)
-
end
-
-
# Allows multiple expectations in the provided block to fail, and then
-
# aggregates them into a single exception, rather than aborting on the
-
# first expectation failure like normal. This allows you to see all
-
# failures from an entire set of expectations without splitting each
-
# off into its own example (which may slow things down if the example
-
# setup is expensive).
-
#
-
# @param label [String] label for this aggregation block, which will be
-
# included in the aggregated exception message.
-
# @param metadata [Hash] additional metadata about this failure aggregation
-
# block. If multiple expectations fail, it will be exposed from the
-
# {Expectations::MultipleExpectationsNotMetError} exception. Mostly
-
# intended for internal RSpec use but you can use it as well.
-
# @yield Block containing as many expectation as you want. The block is
-
# simply yielded to, so you can trust that anything that works outside
-
# the block should work within it.
-
# @raise [Expectations::MultipleExpectationsNotMetError] raised when
-
# multiple expectations fail.
-
# @raise [Expectations::ExpectationNotMetError] raised when a single
-
# expectation fails.
-
# @raise [Exception] other sorts of exceptions will be raised as normal.
-
#
-
# @example
-
# aggregate_failures("verifying response") do
-
# expect(response.status).to eq(200)
-
# expect(response.headers).to include("Content-Type" => "text/plain")
-
# expect(response.body).to include("Success")
-
# end
-
#
-
# @note The implementation of this feature uses a thread-local variable,
-
# which means that if you have an expectation failure in another thread,
-
# it'll abort like normal.
-
1
def aggregate_failures(label=nil, metadata={}, &block)
-
Expectations::FailureAggregator.new(label, metadata).aggregate(&block)
-
end
-
-
# Passes if actual is truthy (anything but false or nil)
-
1
def be_truthy
-
BuiltIn::BeTruthy.new
-
end
-
1
alias_matcher :a_truthy_value, :be_truthy
-
-
# Passes if actual is falsey (false or nil)
-
1
def be_falsey
-
BuiltIn::BeFalsey.new
-
end
-
1
alias_matcher :be_falsy, :be_falsey
-
1
alias_matcher :a_falsey_value, :be_falsey
-
1
alias_matcher :a_falsy_value, :be_falsey
-
-
# Passes if actual is nil
-
1
def be_nil
-
BuiltIn::BeNil.new
-
end
-
1
alias_matcher :a_nil_value, :be_nil
-
-
# @example
-
# expect(actual).to be_truthy
-
# expect(actual).to be_falsey
-
# expect(actual).to be_nil
-
# expect(actual).to be_[arbitrary_predicate](*args)
-
# expect(actual).not_to be_nil
-
# expect(actual).not_to be_[arbitrary_predicate](*args)
-
#
-
# Given true, false, or nil, will pass if actual value is true, false or
-
# nil (respectively). Given no args means the caller should satisfy an if
-
# condition (to be or not to be).
-
#
-
# Predicates are any Ruby method that ends in a "?" and returns true or
-
# false. Given be_ followed by arbitrary_predicate (without the "?"),
-
# RSpec will match convert that into a query against the target object.
-
#
-
# The arbitrary_predicate feature will handle any predicate prefixed with
-
# "be_an_" (e.g. be_an_instance_of), "be_a_" (e.g. be_a_kind_of) or "be_"
-
# (e.g. be_empty), letting you choose the prefix that best suits the
-
# predicate.
-
1
def be(*args)
-
args.empty? ? Matchers::BuiltIn::Be.new : equal(*args)
-
end
-
1
alias_matcher :a_value, :be, :klass => AliasedMatcherWithOperatorSupport
-
-
# passes if target.kind_of?(klass)
-
1
def be_a(klass)
-
be_a_kind_of(klass)
-
end
-
1
alias_method :be_an, :be_a
-
-
# Passes if actual.instance_of?(expected)
-
#
-
# @example
-
# expect(5).to be_an_instance_of(Fixnum)
-
# expect(5).not_to be_an_instance_of(Numeric)
-
# expect(5).not_to be_an_instance_of(Float)
-
1
def be_an_instance_of(expected)
-
BuiltIn::BeAnInstanceOf.new(expected)
-
end
-
1
alias_method :be_instance_of, :be_an_instance_of
-
1
alias_matcher :an_instance_of, :be_an_instance_of
-
-
# Passes if actual.kind_of?(expected)
-
#
-
# @example
-
# expect(5).to be_a_kind_of(Fixnum)
-
# expect(5).to be_a_kind_of(Numeric)
-
# expect(5).not_to be_a_kind_of(Float)
-
1
def be_a_kind_of(expected)
-
BuiltIn::BeAKindOf.new(expected)
-
end
-
1
alias_method :be_kind_of, :be_a_kind_of
-
1
alias_matcher :a_kind_of, :be_a_kind_of
-
-
# Passes if actual.between?(min, max). Works with any Comparable object,
-
# including String, Symbol, Time, or Numeric (Fixnum, Bignum, Integer,
-
# Float, Complex, and Rational).
-
#
-
# By default, `be_between` is inclusive (i.e. passes when given either the max or min value),
-
# but you can make it `exclusive` by chaining that off the matcher.
-
#
-
# @example
-
# expect(5).to be_between(1, 10)
-
# expect(11).not_to be_between(1, 10)
-
# expect(10).not_to be_between(1, 10).exclusive
-
1
def be_between(min, max)
-
BuiltIn::BeBetween.new(min, max)
-
end
-
1
alias_matcher :a_value_between, :be_between
-
-
# Passes if actual == expected +/- delta
-
#
-
# @example
-
# expect(result).to be_within(0.5).of(3.0)
-
# expect(result).not_to be_within(0.5).of(3.0)
-
1
def be_within(delta)
-
BuiltIn::BeWithin.new(delta)
-
end
-
1
alias_matcher :a_value_within, :be_within
-
1
alias_matcher :within, :be_within
-
-
# Applied to a proc, specifies that its execution will cause some value to
-
# change.
-
#
-
# @param [Object] receiver
-
# @param [Symbol] message the message to send the receiver
-
#
-
# You can either pass <tt>receiver</tt> and <tt>message</tt>, or a block,
-
# but not both.
-
#
-
# When passing a block, it must use the `{ ... }` format, not
-
# do/end, as `{ ... }` binds to the `change` method, whereas do/end
-
# would errantly bind to the `expect(..).to` or `expect(...).not_to` method.
-
#
-
# You can chain any of the following off of the end to specify details
-
# about the change:
-
#
-
# * `from`
-
# * `to`
-
#
-
# or any one of:
-
#
-
# * `by`
-
# * `by_at_least`
-
# * `by_at_most`
-
#
-
# @example
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count)
-
#
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count).by(1)
-
#
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count).by_at_least(1)
-
#
-
# expect {
-
# team.add_player(player)
-
# }.to change(roster, :count).by_at_most(1)
-
#
-
# string = "string"
-
# expect {
-
# string.reverse!
-
# }.to change { string }.from("string").to("gnirts")
-
#
-
# string = "string"
-
# expect {
-
# string
-
# }.not_to change { string }.from("string")
-
#
-
# expect {
-
# person.happy_birthday
-
# }.to change(person, :birthday).from(32).to(33)
-
#
-
# expect {
-
# employee.develop_great_new_social_networking_app
-
# }.to change(employee, :title).from("Mail Clerk").to("CEO")
-
#
-
# expect {
-
# doctor.leave_office
-
# }.to change(doctor, :sign).from(/is in/).to(/is out/)
-
#
-
# user = User.new(:type => "admin")
-
# expect {
-
# user.symbolize_type
-
# }.to change(user, :type).from(String).to(Symbol)
-
#
-
# == Notes
-
#
-
# Evaluates `receiver.message` or `block` before and after it
-
# evaluates the block passed to `expect`.
-
#
-
# `expect( ... ).not_to change` supports the form that specifies `from`
-
# (which specifies what you expect the starting, unchanged value to be)
-
# but does not support forms with subsequent calls to `by`, `by_at_least`,
-
# `by_at_most` or `to`.
-
1
def change(receiver=nil, message=nil, &block)
-
BuiltIn::Change.new(receiver, message, &block)
-
end
-
1
alias_matcher :a_block_changing, :change
-
1
alias_matcher :changing, :change
-
-
# Passes if actual contains all of the expected regardless of order.
-
# This works for collections. Pass in multiple args and it will only
-
# pass if all args are found in collection.
-
#
-
# @note This is also available using the `=~` operator with `should`,
-
# but `=~` is not supported with `expect`.
-
#
-
# @example
-
# expect([1, 2, 3]).to contain_exactly(1, 2, 3)
-
# expect([1, 2, 3]).to contain_exactly(1, 3, 2)
-
#
-
# @see #match_array
-
1
def contain_exactly(*items)
-
BuiltIn::ContainExactly.new(items)
-
end
-
1
alias_matcher :a_collection_containing_exactly, :contain_exactly
-
1
alias_matcher :containing_exactly, :contain_exactly
-
-
# Passes if actual covers expected. This works for
-
# Ranges. You can also pass in multiple args
-
# and it will only pass if all args are found in Range.
-
#
-
# @example
-
# expect(1..10).to cover(5)
-
# expect(1..10).to cover(4, 6)
-
# expect(1..10).to cover(4, 6, 11) # fails
-
# expect(1..10).not_to cover(11)
-
# expect(1..10).not_to cover(5) # fails
-
#
-
# ### Warning:: Ruby >= 1.9 only
-
1
def cover(*values)
-
BuiltIn::Cover.new(*values)
-
end
-
1
alias_matcher :a_range_covering, :cover
-
1
alias_matcher :covering, :cover
-
-
# Matches if the actual value ends with the expected value(s). In the case
-
# of a string, matches against the last `expected.length` characters of the
-
# actual string. In the case of an array, matches against the last
-
# `expected.length` elements of the actual array.
-
#
-
# @example
-
# expect("this string").to end_with "string"
-
# expect([0, 1, 2, 3, 4]).to end_with 4
-
# expect([0, 2, 3, 4, 4]).to end_with 3, 4
-
1
def end_with(*expected)
-
BuiltIn::EndWith.new(*expected)
-
end
-
1
alias_matcher :a_collection_ending_with, :end_with
-
1
alias_matcher :a_string_ending_with, :end_with
-
1
alias_matcher :ending_with, :end_with
-
-
# Passes if <tt>actual == expected</tt>.
-
#
-
# See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more
-
# information about equality in Ruby.
-
#
-
# @example
-
# expect(5).to eq(5)
-
# expect(5).not_to eq(3)
-
1
def eq(expected)
-
23
BuiltIn::Eq.new(expected)
-
end
-
1
alias_matcher :an_object_eq_to, :eq
-
1
alias_matcher :eq_to, :eq
-
-
# Passes if `actual.eql?(expected)`
-
#
-
# See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more
-
# information about equality in Ruby.
-
#
-
# @example
-
# expect(5).to eql(5)
-
# expect(5).not_to eql(3)
-
1
def eql(expected)
-
BuiltIn::Eql.new(expected)
-
end
-
1
alias_matcher :an_object_eql_to, :eql
-
1
alias_matcher :eql_to, :eql
-
-
# Passes if <tt>actual.equal?(expected)</tt> (object identity).
-
#
-
# See http://www.ruby-doc.org/core/classes/Object.html#M001057 for more
-
# information about equality in Ruby.
-
#
-
# @example
-
# expect(5).to equal(5) # Fixnums are equal
-
# expect("5").not_to equal("5") # Strings that look the same are not the same object
-
1
def equal(expected)
-
BuiltIn::Equal.new(expected)
-
end
-
1
alias_matcher :an_object_equal_to, :equal
-
1
alias_matcher :equal_to, :equal
-
-
# Passes if `actual.exist?` or `actual.exists?`
-
#
-
# @example
-
# expect(File).to exist("path/to/file")
-
1
def exist(*args)
-
BuiltIn::Exist.new(*args)
-
end
-
1
alias_matcher :an_object_existing, :exist
-
1
alias_matcher :existing, :exist
-
-
# Passes if actual's attribute values match the expected attributes hash.
-
# This works no matter how you define your attribute readers.
-
#
-
# @example
-
# Person = Struct.new(:name, :age)
-
# person = Person.new("Bob", 32)
-
#
-
# expect(person).to have_attributes(:name => "Bob", :age => 32)
-
# expect(person).to have_attributes(:name => a_string_starting_with("B"), :age => (a_value > 30) )
-
#
-
# @note It will fail if actual doesn't respond to any of the expected attributes.
-
#
-
# @example
-
# expect(person).to have_attributes(:color => "red")
-
1
def have_attributes(expected)
-
BuiltIn::HaveAttributes.new(expected)
-
end
-
1
alias_matcher :an_object_having_attributes, :have_attributes
-
-
# Passes if actual includes expected. This works for
-
# collections and Strings. You can also pass in multiple args
-
# and it will only pass if all args are found in collection.
-
#
-
# @example
-
# expect([1,2,3]).to include(3)
-
# expect([1,2,3]).to include(2,3)
-
# expect([1,2,3]).to include(2,3,4) # fails
-
# expect([1,2,3]).not_to include(4)
-
# expect("spread").to include("read")
-
# expect("spread").not_to include("red")
-
# expect(:a => 1, :b => 2).to include(:a)
-
# expect(:a => 1, :b => 2).to include(:a, :b)
-
# expect(:a => 1, :b => 2).to include(:a => 1)
-
# expect(:a => 1, :b => 2).to include(:b => 2, :a => 1)
-
# expect(:a => 1, :b => 2).to include(:c) # fails
-
# expect(:a => 1, :b => 2).not_to include(:a => 2)
-
1
def include(*expected)
-
BuiltIn::Include.new(*expected)
-
end
-
1
alias_matcher :a_collection_including, :include
-
1
alias_matcher :a_string_including, :include
-
1
alias_matcher :a_hash_including, :include
-
1
alias_matcher :including, :include
-
-
# Passes if the provided matcher passes when checked against all
-
# elements of the collection.
-
#
-
# @example
-
# expect([1, 3, 5]).to all be_odd
-
# expect([1, 3, 6]).to all be_odd # fails
-
#
-
# @note The negative form `not_to all` is not supported. Instead
-
# use `not_to include` or pass a negative form of a matcher
-
# as the argument (e.g. `all exclude(:foo)`).
-
#
-
# @note You can also use this with compound matchers as well.
-
#
-
# @example
-
# expect([1, 3, 5]).to all( be_odd.and be_an(Integer) )
-
1
def all(expected)
-
BuiltIn::All.new(expected)
-
end
-
-
# Given a `Regexp` or `String`, passes if `actual.match(pattern)`
-
# Given an arbitrary nested data structure (e.g. arrays and hashes),
-
# matches if `expected === actual` || `actual == expected` for each
-
# pair of elements.
-
#
-
# @example
-
# expect(email).to match(/^([^\s]+)((?:[-a-z0-9]+\.)+[a-z]{2,})$/i)
-
# expect(email).to match("@example.com")
-
#
-
# @example
-
# hash = {
-
# :a => {
-
# :b => ["foo", 5],
-
# :c => { :d => 2.05 }
-
# }
-
# }
-
#
-
# expect(hash).to match(
-
# :a => {
-
# :b => a_collection_containing_exactly(
-
# a_string_starting_with("f"),
-
# an_instance_of(Fixnum)
-
# ),
-
# :c => { :d => (a_value < 3) }
-
# }
-
# )
-
#
-
# @note The `match_regex` alias is deprecated and is not recommended for use.
-
# It was added in 2.12.1 to facilitate its use from within custom
-
# matchers (due to how the custom matcher DSL was evaluated in 2.x,
-
# `match` could not be used there), but is no longer needed in 3.x.
-
1
def match(expected)
-
BuiltIn::Match.new(expected)
-
end
-
1
alias_matcher :match_regex, :match
-
1
alias_matcher :an_object_matching, :match
-
1
alias_matcher :a_string_matching, :match
-
1
alias_matcher :matching, :match
-
-
# An alternate form of `contain_exactly` that accepts
-
# the expected contents as a single array arg rather
-
# that splatted out as individual items.
-
#
-
# @example
-
# expect(results).to contain_exactly(1, 2)
-
# # is identical to:
-
# expect(results).to match_array([1, 2])
-
#
-
# @see #contain_exactly
-
1
def match_array(items)
-
contain_exactly(*items)
-
end
-
-
# With no arg, passes if the block outputs `to_stdout` or `to_stderr`.
-
# With a string, passes if the block outputs that specific string `to_stdout` or `to_stderr`.
-
# With a regexp or matcher, passes if the block outputs a string `to_stdout` or `to_stderr` that matches.
-
#
-
# To capture output from any spawned subprocess as well, use `to_stdout_from_any_process` or
-
# `to_stderr_from_any_process`. Output from any process that inherits the main process's corresponding
-
# standard stream will be captured.
-
#
-
# @example
-
# expect { print 'foo' }.to output.to_stdout
-
# expect { print 'foo' }.to output('foo').to_stdout
-
# expect { print 'foo' }.to output(/foo/).to_stdout
-
#
-
# expect { do_something }.to_not output.to_stdout
-
#
-
# expect { warn('foo') }.to output.to_stderr
-
# expect { warn('foo') }.to output('foo').to_stderr
-
# expect { warn('foo') }.to output(/foo/).to_stderr
-
#
-
# expect { do_something }.to_not output.to_stderr
-
#
-
# expect { system('echo foo') }.to output("foo\n").to_stdout_from_any_process
-
# expect { system('echo foo', out: :err) }.to output("foo\n").to_stderr_from_any_process
-
#
-
# @note `to_stdout` and `to_stderr` work by temporarily replacing `$stdout` or `$stderr`,
-
# so they're not able to intercept stream output that explicitly uses `STDOUT`/`STDERR`
-
# or that uses a reference to `$stdout`/`$stderr` that was stored before the
-
# matcher was used.
-
# @note `to_stdout_from_any_process` and `to_stderr_from_any_process` use Tempfiles, and
-
# are thus significantly (~30x) slower than `to_stdout` and `to_stderr`.
-
1
def output(expected=nil)
-
BuiltIn::Output.new(expected)
-
end
-
1
alias_matcher :a_block_outputting, :output
-
-
# With no args, matches if any error is raised.
-
# With a named error, matches only if that specific error is raised.
-
# With a named error and messsage specified as a String, matches only if both match.
-
# With a named error and messsage specified as a Regexp, matches only if both match.
-
# Pass an optional block to perform extra verifications on the exception matched
-
#
-
# @example
-
# expect { do_something_risky }.to raise_error
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError)
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError) { |error| expect(error.data).to eq 42 }
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError, "that was too risky")
-
# expect { do_something_risky }.to raise_error(PoorRiskDecisionError, /oo ri/)
-
#
-
# expect { do_something_risky }.not_to raise_error
-
1
def raise_error(error=nil, message=nil, &block)
-
BuiltIn::RaiseError.new(error, message, &block)
-
end
-
1
alias_method :raise_exception, :raise_error
-
-
1
alias_matcher :a_block_raising, :raise_error do |desc|
-
desc.sub("raise", "a block raising")
-
end
-
-
1
alias_matcher :raising, :raise_error do |desc|
-
desc.sub("raise", "raising")
-
end
-
-
# Matches if the target object responds to all of the names
-
# provided. Names can be Strings or Symbols.
-
#
-
# @example
-
# expect("string").to respond_to(:length)
-
#
-
1
def respond_to(*names)
-
BuiltIn::RespondTo.new(*names)
-
end
-
1
alias_matcher :an_object_responding_to, :respond_to
-
1
alias_matcher :responding_to, :respond_to
-
-
# Passes if the submitted block returns true. Yields target to the
-
# block.
-
#
-
# Generally speaking, this should be thought of as a last resort when
-
# you can't find any other way to specify the behaviour you wish to
-
# specify.
-
#
-
# If you do find yourself in such a situation, you could always write
-
# a custom matcher, which would likely make your specs more expressive.
-
#
-
# @param description [String] optional description to be used for this matcher.
-
#
-
# @example
-
# expect(5).to satisfy { |n| n > 3 }
-
# expect(5).to satisfy("be greater than 3") { |n| n > 3 }
-
1
def satisfy(description="satisfy block", &block)
-
BuiltIn::Satisfy.new(description, &block)
-
end
-
1
alias_matcher :an_object_satisfying, :satisfy
-
1
alias_matcher :satisfying, :satisfy
-
-
# Matches if the actual value starts with the expected value(s). In the
-
# case of a string, matches against the first `expected.length` characters
-
# of the actual string. In the case of an array, matches against the first
-
# `expected.length` elements of the actual array.
-
#
-
# @example
-
# expect("this string").to start_with "this s"
-
# expect([0, 1, 2, 3, 4]).to start_with 0
-
# expect([0, 2, 3, 4, 4]).to start_with 0, 1
-
1
def start_with(*expected)
-
BuiltIn::StartWith.new(*expected)
-
end
-
1
alias_matcher :a_collection_starting_with, :start_with
-
1
alias_matcher :a_string_starting_with, :start_with
-
1
alias_matcher :starting_with, :start_with
-
-
# Given no argument, matches if a proc throws any Symbol.
-
#
-
# Given a Symbol, matches if the given proc throws the specified Symbol.
-
#
-
# Given a Symbol and an arg, matches if the given proc throws the
-
# specified Symbol with the specified arg.
-
#
-
# @example
-
# expect { do_something_risky }.to throw_symbol
-
# expect { do_something_risky }.to throw_symbol(:that_was_risky)
-
# expect { do_something_risky }.to throw_symbol(:that_was_risky, 'culprit')
-
#
-
# expect { do_something_risky }.not_to throw_symbol
-
# expect { do_something_risky }.not_to throw_symbol(:that_was_risky)
-
# expect { do_something_risky }.not_to throw_symbol(:that_was_risky, 'culprit')
-
1
def throw_symbol(expected_symbol=nil, expected_arg=nil)
-
BuiltIn::ThrowSymbol.new(expected_symbol, expected_arg)
-
end
-
-
1
alias_matcher :a_block_throwing, :throw_symbol do |desc|
-
desc.sub("throw", "a block throwing")
-
end
-
-
1
alias_matcher :throwing, :throw_symbol do |desc|
-
desc.sub("throw", "throwing")
-
end
-
-
# Passes if the method called in the expect block yields, regardless
-
# of whether or not arguments are yielded.
-
#
-
# @example
-
# expect { |b| 5.tap(&b) }.to yield_control
-
# expect { |b| "a".to_sym(&b) }.not_to yield_control
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
1
def yield_control
-
BuiltIn::YieldControl.new
-
end
-
1
alias_matcher :a_block_yielding_control, :yield_control
-
1
alias_matcher :yielding_control, :yield_control
-
-
# Passes if the method called in the expect block yields with
-
# no arguments. Fails if it does not yield, or yields with arguments.
-
#
-
# @example
-
# expect { |b| User.transaction(&b) }.to yield_with_no_args
-
# expect { |b| 5.tap(&b) }.not_to yield_with_no_args # because it yields with `5`
-
# expect { |b| "a".to_sym(&b) }.not_to yield_with_no_args # because it does not yield
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
# @note This matcher is not designed for use with methods that yield
-
# multiple times.
-
1
def yield_with_no_args
-
BuiltIn::YieldWithNoArgs.new
-
end
-
1
alias_matcher :a_block_yielding_with_no_args, :yield_with_no_args
-
1
alias_matcher :yielding_with_no_args, :yield_with_no_args
-
-
# Given no arguments, matches if the method called in the expect
-
# block yields with arguments (regardless of what they are or how
-
# many there are).
-
#
-
# Given arguments, matches if the method called in the expect block
-
# yields with arguments that match the given arguments.
-
#
-
# Argument matching is done using `===` (the case match operator)
-
# and `==`. If the expected and actual arguments match with either
-
# operator, the matcher will pass.
-
#
-
# @example
-
# expect { |b| 5.tap(&b) }.to yield_with_args # because #tap yields an arg
-
# expect { |b| 5.tap(&b) }.to yield_with_args(5) # because 5 == 5
-
# expect { |b| 5.tap(&b) }.to yield_with_args(Fixnum) # because Fixnum === 5
-
# expect { |b| File.open("f.txt", &b) }.to yield_with_args(/txt/) # because /txt/ === "f.txt"
-
#
-
# expect { |b| User.transaction(&b) }.not_to yield_with_args # because it yields no args
-
# expect { |b| 5.tap(&b) }.not_to yield_with_args(1, 2, 3)
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
# @note This matcher is not designed for use with methods that yield
-
# multiple times.
-
1
def yield_with_args(*args)
-
BuiltIn::YieldWithArgs.new(*args)
-
end
-
1
alias_matcher :a_block_yielding_with_args, :yield_with_args
-
1
alias_matcher :yielding_with_args, :yield_with_args
-
-
# Designed for use with methods that repeatedly yield (such as
-
# iterators). Passes if the method called in the expect block yields
-
# multiple times with arguments matching those given.
-
#
-
# Argument matching is done using `===` (the case match operator)
-
# and `==`. If the expected and actual arguments match with either
-
# operator, the matcher will pass.
-
#
-
# @example
-
# expect { |b| [1, 2, 3].each(&b) }.to yield_successive_args(1, 2, 3)
-
# expect { |b| { :a => 1, :b => 2 }.each(&b) }.to yield_successive_args([:a, 1], [:b, 2])
-
# expect { |b| [1, 2, 3].each(&b) }.not_to yield_successive_args(1, 2)
-
#
-
# @note Your expect block must accept a parameter and pass it on to
-
# the method-under-test as a block.
-
1
def yield_successive_args(*args)
-
BuiltIn::YieldSuccessiveArgs.new(*args)
-
end
-
1
alias_matcher :a_block_yielding_successive_args, :yield_successive_args
-
1
alias_matcher :yielding_successive_args, :yield_successive_args
-
-
# Delegates to {RSpec::Expectations.configuration}.
-
# This is here because rspec-core's `expect_with` option
-
# looks for a `configuration` method on the mixin
-
# (`RSpec::Matchers`) to yield to a block.
-
# @return [RSpec::Expectations::Configuration] the configuration object
-
1
def self.configuration
-
3
Expectations.configuration
-
end
-
-
1
private
-
-
1
BE_PREDICATE_REGEX = /^(be_(?:an?_)?)(.*)/
-
1
HAS_REGEX = /^(?:have_)(.*)/
-
1
DYNAMIC_MATCHER_REGEX = Regexp.union(BE_PREDICATE_REGEX, HAS_REGEX)
-
-
1
def method_missing(method, *args, &block)
-
case method.to_s
-
when BE_PREDICATE_REGEX
-
BuiltIn::BePredicate.new(method, *args, &block)
-
when HAS_REGEX
-
BuiltIn::Has.new(method, *args, &block)
-
else
-
super
-
end
-
end
-
-
1
if RUBY_VERSION.to_f >= 1.9
-
1
def respond_to_missing?(method, *)
-
method =~ DYNAMIC_MATCHER_REGEX || super
-
end
-
else # for 1.8.7
-
# :nocov:
-
skipped
def respond_to?(method, *)
-
skipped
method = method.to_s
-
skipped
method =~ DYNAMIC_MATCHER_REGEX || super
-
skipped
end
-
skipped
public :respond_to?
-
# :nocov:
-
end
-
-
# @api private
-
1
def self.is_a_matcher?(obj)
-
return true if ::RSpec::Matchers::BuiltIn::BaseMatcher === obj
-
begin
-
return false if obj.respond_to?(:i_respond_to_everything_so_im_not_really_a_matcher)
-
rescue NoMethodError
-
# Some objects, like BasicObject, don't implemented standard
-
# reflection methods.
-
return false
-
end
-
return false unless obj.respond_to?(:matches?)
-
-
obj.respond_to?(:failure_message) ||
-
obj.respond_to?(:failure_message_for_should) # support legacy matchers
-
end
-
-
1
::RSpec::Support.register_matcher_definition do |obj|
-
is_a_matcher?(obj)
-
end
-
-
# @api private
-
1
def self.is_a_describable_matcher?(obj)
-
is_a_matcher?(obj) && obj.respond_to?(:description)
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# Decorator that wraps a matcher and overrides `description`
-
# using the provided block in order to support an alias
-
# of a matcher. This is intended for use when composing
-
# matchers, so that you can use an expression like
-
# `include( a_value_within(0.1).of(3) )` rather than
-
# `include( be_within(0.1).of(3) )`, and have the corresponding
-
# description read naturally.
-
#
-
# @api private
-
1
class AliasedMatcher < MatcherDelegator
-
1
def initialize(base_matcher, description_block)
-
@description_block = description_block
-
super(base_matcher)
-
end
-
-
# Forward messages on to the wrapped matcher.
-
# Since many matchers provide a fluent interface
-
# (e.g. `a_value_within(0.1).of(3)`), we need to wrap
-
# the returned value if it responds to `description`,
-
# so that our override can be applied when it is eventually
-
# used.
-
1
def method_missing(*)
-
return_val = super
-
return return_val unless RSpec::Matchers.is_a_matcher?(return_val)
-
self.class.new(return_val, @description_block)
-
end
-
-
# Provides the description of the aliased matcher. Aliased matchers
-
# are designed to behave identically to the original matcher except
-
# for the description and failure messages. The description is different
-
# to reflect the aliased name.
-
#
-
# @api private
-
1
def description
-
@description_block.call(super)
-
end
-
-
# Provides the failure_message of the aliased matcher. Aliased matchers
-
# are designed to behave identically to the original matcher except
-
# for the description and failure messages. The failure_message is different
-
# to reflect the aliased name.
-
#
-
# @api private
-
1
def failure_message
-
@description_block.call(super)
-
end
-
-
# Provides the failure_message_when_negated of the aliased matcher. Aliased matchers
-
# are designed to behave identically to the original matcher except
-
# for the description and failure messages. The failure_message_when_negated is different
-
# to reflect the aliased name.
-
#
-
# @api private
-
1
def failure_message_when_negated
-
@description_block.call(super)
-
end
-
end
-
-
# Decorator used for matchers that have special implementations of
-
# operators like `==` and `===`.
-
# @private
-
1
class AliasedMatcherWithOperatorSupport < AliasedMatcher
-
# We undef these so that they get delegated via `method_missing`.
-
1
undef ==
-
1
undef ===
-
end
-
-
# @private
-
1
class AliasedNegatedMatcher < AliasedMatcher
-
1
def matches?(*args, &block)
-
if @base_matcher.respond_to?(:does_not_match?)
-
@base_matcher.does_not_match?(*args, &block)
-
else
-
!super
-
end
-
end
-
-
1
def does_not_match?(*args, &block)
-
@base_matcher.matches?(*args, &block)
-
end
-
-
1
def failure_message
-
optimal_failure_message(__method__, :failure_message_when_negated)
-
end
-
-
1
def failure_message_when_negated
-
optimal_failure_message(__method__, :failure_message)
-
end
-
-
1
private
-
-
1
DefaultFailureMessages = BuiltIn::BaseMatcher::DefaultFailureMessages
-
-
# For a matcher that uses the default failure messages, we prefer to
-
# use the override provided by the `description_block`, because it
-
# includes the phrasing that the user has expressed a preference for
-
# by going through the effort of defining a negated matcher.
-
#
-
# However, if the override didn't actually change anything, then we
-
# should return the opposite failure message instead -- the overriden
-
# message is going to be confusing if we return it as-is, as it represents
-
# the non-negated failure message for a negated match (or vice versa).
-
1
def optimal_failure_message(same, inverted)
-
if DefaultFailureMessages.has_default_failure_messages?(@base_matcher)
-
base_message = @base_matcher.__send__(same)
-
overriden = @description_block.call(base_message)
-
return overriden if overriden != base_message
-
end
-
-
@base_matcher.__send__(inverted)
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_matchers "built_in/base_matcher"
-
-
1
module RSpec
-
1
module Matchers
-
# Container module for all built-in matchers. The matcher classes are here
-
# (rather than directly under `RSpec::Matchers`) in order to prevent name
-
# collisions, since `RSpec::Matchers` gets included into the user's namespace.
-
#
-
# Autoloading is used to delay when the matcher classes get loaded, allowing
-
# rspec-matchers to boot faster, and avoiding loading matchers the user is
-
# not using.
-
1
module BuiltIn
-
1
autoload :BeAKindOf, 'rspec/matchers/built_in/be_kind_of'
-
1
autoload :BeAnInstanceOf, 'rspec/matchers/built_in/be_instance_of'
-
1
autoload :BeBetween, 'rspec/matchers/built_in/be_between'
-
1
autoload :Be, 'rspec/matchers/built_in/be'
-
1
autoload :BeComparedTo, 'rspec/matchers/built_in/be'
-
1
autoload :BeFalsey, 'rspec/matchers/built_in/be'
-
1
autoload :BeNil, 'rspec/matchers/built_in/be'
-
1
autoload :BePredicate, 'rspec/matchers/built_in/be'
-
1
autoload :BeTruthy, 'rspec/matchers/built_in/be'
-
1
autoload :BeWithin, 'rspec/matchers/built_in/be_within'
-
1
autoload :Change, 'rspec/matchers/built_in/change'
-
1
autoload :Compound, 'rspec/matchers/built_in/compound'
-
1
autoload :ContainExactly, 'rspec/matchers/built_in/contain_exactly'
-
1
autoload :Cover, 'rspec/matchers/built_in/cover'
-
1
autoload :EndWith, 'rspec/matchers/built_in/start_or_end_with'
-
1
autoload :Eq, 'rspec/matchers/built_in/eq'
-
1
autoload :Eql, 'rspec/matchers/built_in/eql'
-
1
autoload :Equal, 'rspec/matchers/built_in/equal'
-
1
autoload :Exist, 'rspec/matchers/built_in/exist'
-
1
autoload :Has, 'rspec/matchers/built_in/has'
-
1
autoload :HaveAttributes, 'rspec/matchers/built_in/have_attributes'
-
1
autoload :Include, 'rspec/matchers/built_in/include'
-
1
autoload :All, 'rspec/matchers/built_in/all'
-
1
autoload :Match, 'rspec/matchers/built_in/match'
-
1
autoload :NegativeOperatorMatcher, 'rspec/matchers/built_in/operators'
-
1
autoload :OperatorMatcher, 'rspec/matchers/built_in/operators'
-
1
autoload :Output, 'rspec/matchers/built_in/output'
-
1
autoload :PositiveOperatorMatcher, 'rspec/matchers/built_in/operators'
-
1
autoload :RaiseError, 'rspec/matchers/built_in/raise_error'
-
1
autoload :RespondTo, 'rspec/matchers/built_in/respond_to'
-
1
autoload :Satisfy, 'rspec/matchers/built_in/satisfy'
-
1
autoload :StartWith, 'rspec/matchers/built_in/start_or_end_with'
-
1
autoload :ThrowSymbol, 'rspec/matchers/built_in/throw_symbol'
-
1
autoload :YieldControl, 'rspec/matchers/built_in/yield'
-
1
autoload :YieldSuccessiveArgs, 'rspec/matchers/built_in/yield'
-
1
autoload :YieldWithArgs, 'rspec/matchers/built_in/yield'
-
1
autoload :YieldWithNoArgs, 'rspec/matchers/built_in/yield'
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
#
-
# Used _internally_ as a base class for matchers that ship with
-
# rspec-expectations and rspec-rails.
-
#
-
# ### Warning:
-
#
-
# This class is for internal use, and subject to change without notice. We
-
# strongly recommend that you do not base your custom matchers on this
-
# class. If/when this changes, we will announce it and remove this warning.
-
1
class BaseMatcher
-
1
include RSpec::Matchers::Composable
-
-
# @api private
-
# Used to detect when no arg is passed to `initialize`.
-
# `nil` cannot be used because it's a valid value to pass.
-
1
UNDEFINED = Object.new.freeze
-
-
# @private
-
1
attr_reader :actual, :expected, :rescued_exception
-
-
1
def initialize(expected=UNDEFINED)
-
23
@expected = expected unless UNDEFINED.equal?(expected)
-
end
-
-
# @api private
-
# Indicates if the match is successful. Delegates to `match`, which
-
# should be defined on a subclass. Takes care of consistently
-
# initializing the `actual` attribute.
-
1
def matches?(actual)
-
23
@actual = actual
-
23
match(expected, actual)
-
end
-
-
# @api private
-
# Used to wrap a block of code that will indicate failure by
-
# raising one of the named exceptions.
-
#
-
# This is used by rspec-rails for some of its matchers that
-
# wrap rails' assertions.
-
1
def match_unless_raises(*exceptions)
-
exceptions.unshift Exception if exceptions.empty?
-
begin
-
yield
-
true
-
rescue *exceptions => @rescued_exception
-
false
-
end
-
end
-
-
# @api private
-
# Generates a description using {EnglishPhrasing}.
-
# @return [String]
-
1
def description
-
desc = EnglishPhrasing.split_words(self.class.matcher_name)
-
desc << EnglishPhrasing.list(@expected) if defined?(@expected)
-
desc
-
end
-
-
# @api private
-
# Matchers are not diffable by default. Override this to make your
-
# subclass diffable.
-
1
def diffable?
-
false
-
end
-
-
# @api private
-
# Most matchers are value matchers (i.e. meant to work with `expect(value)`)
-
# rather than block matchers (i.e. meant to work with `expect { }`), so
-
# this defaults to false. Block matchers must override this to return true.
-
1
def supports_block_expectations?
-
false
-
end
-
-
# @api private
-
1
def expects_call_stack_jump?
-
false
-
end
-
-
# @private
-
1
def expected_formatted
-
RSpec::Support::ObjectFormatter.format(@expected)
-
end
-
-
# @private
-
1
def actual_formatted
-
RSpec::Support::ObjectFormatter.format(@actual)
-
end
-
-
# @private
-
1
def self.matcher_name
-
@matcher_name ||= underscore(name.split("::").last)
-
end
-
-
# @private
-
# Borrowed from ActiveSupport.
-
1
def self.underscore(camel_cased_word)
-
word = camel_cased_word.to_s.dup
-
word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
-
word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
-
word.tr!("-", "_")
-
word.downcase!
-
word
-
end
-
1
private_class_method :underscore
-
-
1
private
-
-
1
def assert_ivars(*expected_ivars)
-
return unless (expected_ivars - present_ivars).any?
-
ivar_list = EnglishPhrasing.list(expected_ivars)
-
raise "#{self.class.name} needs to supply#{ivar_list}"
-
end
-
-
1
if RUBY_VERSION.to_f < 1.9
-
# :nocov:
-
skipped
def present_ivars
-
skipped
instance_variables.map { |v| v.to_sym }
-
skipped
end
-
# :nocov:
-
else
-
1
alias present_ivars instance_variables
-
end
-
-
# @private
-
1
module HashFormatting
-
# `{ :a => 5, :b => 2 }.inspect` produces:
-
#
-
# {:a=>5, :b=>2}
-
#
-
# ...but it looks much better as:
-
#
-
# {:a => 5, :b => 2}
-
#
-
# This is idempotent and safe to run on a string multiple times.
-
1
def improve_hash_formatting(inspect_string)
-
inspect_string.gsub(/(\S)=>(\S)/, '\1 => \2')
-
end
-
1
module_function :improve_hash_formatting
-
end
-
-
1
include HashFormatting
-
-
# @api private
-
# Provides default implementations of failure messages, based on the `description`.
-
1
module DefaultFailureMessages
-
# @api private
-
# Provides a good generic failure message. Based on `description`.
-
# When subclassing, if you are not satisfied with this failure message
-
# you often only need to override `description`.
-
# @return [String]
-
1
def failure_message
-
"expected #{description_of @actual} to #{description}"
-
end
-
-
# @api private
-
# Provides a good generic negative failure message. Based on `description`.
-
# When subclassing, if you are not satisfied with this failure message
-
# you often only need to override `description`.
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected #{description_of @actual} not to #{description}"
-
end
-
-
# @private
-
1
def self.has_default_failure_messages?(matcher)
-
matcher.method(:failure_message).owner == self &&
-
matcher.method(:failure_message_when_negated).owner == self
-
rescue NameError
-
false
-
end
-
end
-
-
1
include DefaultFailureMessages
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `be_truthy`.
-
# Not intended to be instantiated directly.
-
1
class BeTruthy < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: truthy value\n got: #{actual_formatted}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected: falsey value\n got: #{actual_formatted}"
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
!!actual
-
end
-
end
-
-
# @api private
-
# Provides the implementation for `be_falsey`.
-
# Not intended to be instantiated directly.
-
1
class BeFalsey < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: falsey value\n got: #{actual_formatted}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected: truthy value\n got: #{actual_formatted}"
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
!actual
-
end
-
end
-
-
# @api private
-
# Provides the implementation for `be_nil`.
-
# Not intended to be instantiated directly.
-
1
class BeNil < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: nil\n got: #{actual_formatted}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected: not nil\n got: nil"
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
actual.nil?
-
end
-
end
-
-
# @private
-
1
module BeHelpers
-
1
private
-
-
1
def args_to_s
-
@args.empty? ? "" : parenthesize(inspected_args.join(', '))
-
end
-
-
1
def parenthesize(string)
-
"(#{string})"
-
end
-
-
1
def inspected_args
-
@args.map { |a| RSpec::Support::ObjectFormatter.format(a) }
-
end
-
-
1
def expected_to_sentence
-
EnglishPhrasing.split_words(@expected)
-
end
-
-
1
def args_to_sentence
-
EnglishPhrasing.list(@args)
-
end
-
end
-
-
# @api private
-
# Provides the implementation for `be`.
-
# Not intended to be instantiated directly.
-
1
class Be < BaseMatcher
-
1
include BeHelpers
-
-
1
def initialize(*args)
-
@args = args
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected #{actual_formatted} to evaluate to true"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"expected #{actual_formatted} to evaluate to false"
-
end
-
-
1
[:==, :<, :<=, :>=, :>, :===, :=~].each do |operator|
-
7
define_method operator do |operand|
-
BeComparedTo.new(operand, operator)
-
end
-
end
-
-
1
private
-
-
1
def match(_, actual)
-
!!actual
-
end
-
end
-
-
# @api private
-
# Provides the implementation of `be <operator> value`.
-
# Not intended to be instantiated directly.
-
1
class BeComparedTo < BaseMatcher
-
1
include BeHelpers
-
-
1
def initialize(operand, operator)
-
@expected, @operator = operand, operator
-
@args = []
-
end
-
-
1
def matches?(actual)
-
@actual = actual
-
@actual.__send__ @operator, @expected
-
rescue ArgumentError
-
false
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"expected: #{@operator} #{expected_formatted}\n got: #{@operator.to_s.gsub(/./, ' ')} #{actual_formatted}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
message = "`expect(#{actual_formatted}).not_to be #{@operator} #{expected_formatted}`"
-
if [:<, :>, :<=, :>=].include?(@operator)
-
message + " not only FAILED, it is a bit confusing."
-
else
-
message
-
end
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"be #{@operator} #{expected_to_sentence}#{args_to_sentence}"
-
end
-
end
-
-
# @api private
-
# Provides the implementation of `be_<predicate>`.
-
# Not intended to be instantiated directly.
-
1
class BePredicate < BaseMatcher
-
1
include BeHelpers
-
-
1
def initialize(*args, &block)
-
@expected = parse_expected(args.shift)
-
@args = args
-
@block = block
-
end
-
-
1
def matches?(actual, &block)
-
@actual = actual
-
@block ||= block
-
predicate_accessible? && predicate_matches?
-
end
-
-
1
def does_not_match?(actual, &block)
-
@actual = actual
-
@block ||= block
-
predicate_accessible? && !predicate_matches?
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message
-
failure_message_expecting(true)
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
failure_message_expecting(false)
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"#{prefix_to_sentence}#{expected_to_sentence}#{args_to_sentence}"
-
end
-
-
1
private
-
-
1
def predicate_accessible?
-
actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
-
end
-
-
# support 1.8.7, evaluate once at load time for performance
-
1
if String === methods.first
-
# :nocov:
-
skipped
def private_predicate?
-
skipped
@actual.private_methods.include? predicate.to_s
-
skipped
end
-
# :nocov:
-
else
-
1
def private_predicate?
-
@actual.private_methods.include? predicate
-
end
-
end
-
-
1
def predicate_matches?
-
method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
-
@predicate_matches = actual.__send__(method_name, *@args, &@block)
-
end
-
-
1
def predicate
-
:"#{@expected}?"
-
end
-
-
1
def present_tense_predicate
-
:"#{@expected}s?"
-
end
-
-
1
def parse_expected(expected)
-
@prefix, expected = prefix_and_expected(expected)
-
expected
-
end
-
-
1
def prefix_and_expected(symbol)
-
Matchers::BE_PREDICATE_REGEX.match(symbol.to_s).captures.compact
-
end
-
-
1
def prefix_to_sentence
-
EnglishPhrasing.split_words(@prefix)
-
end
-
-
1
def failure_message_expecting(value)
-
validity_message ||
-
"expected `#{actual_formatted}.#{predicate}#{args_to_s}` to return #{value}, got #{description_of @predicate_matches}"
-
end
-
-
1
def validity_message
-
return nil if predicate_accessible?
-
-
msg = "expected #{@actual} to respond to `#{predicate}`"
-
-
if private_predicate?
-
msg << " but `#{predicate}` is a private method"
-
elsif predicate == :true?
-
msg << " or perhaps you meant `be true` or `be_truthy`"
-
elsif predicate == :false?
-
msg << " or perhaps you meant `be false` or `be_falsey`"
-
end
-
-
msg
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `contain_exactly` and `match_array`.
-
# Not intended to be instantiated directly.
-
1
class ContainExactly < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
if Array === actual
-
message = "expected collection contained: #{description_of(safe_sort(surface_descriptions_in expected))}\n"
-
message += "actual collection contained: #{description_of(safe_sort(actual))}\n"
-
message += "the missing elements were: #{description_of(safe_sort(surface_descriptions_in missing_items))}\n" unless missing_items.empty?
-
message += "the extra elements were: #{description_of(safe_sort(extra_items))}\n" unless extra_items.empty?
-
message
-
else
-
"expected a collection that can be converted to an array with " \
-
"`#to_ary` or `#to_a`, but got #{actual_formatted}"
-
end
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
list = EnglishPhrasing.list(surface_descriptions_in(expected))
-
"expected #{actual_formatted} not to contain exactly#{list}"
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
list = EnglishPhrasing.list(surface_descriptions_in(expected))
-
"contain exactly#{list}"
-
end
-
-
1
private
-
-
1
def match(_expected, _actual)
-
return false unless convert_actual_to_an_array
-
match_when_sorted? || (extra_items.empty? && missing_items.empty?)
-
end
-
-
# This cannot always work (e.g. when dealing with unsortable items,
-
# or matchers as expected items), but it's practically free compared to
-
# the slowness of the full matching algorithm, and in common cases this
-
# works, so it's worth a try.
-
1
def match_when_sorted?
-
values_match?(safe_sort(expected), safe_sort(actual))
-
end
-
-
1
def convert_actual_to_an_array
-
if actual.respond_to?(:to_ary)
-
@actual = actual.to_ary
-
elsif should_enumerate?(actual) && actual.respond_to?(:to_a)
-
@actual = actual.to_a
-
else
-
return false
-
end
-
end
-
-
1
def safe_sort(array)
-
array.sort
-
rescue Exception
-
array
-
end
-
-
1
def missing_items
-
@missing_items ||= best_solution.unmatched_expected_indexes.map do |index|
-
expected[index]
-
end
-
end
-
-
1
def extra_items
-
@extra_items ||= best_solution.unmatched_actual_indexes.map do |index|
-
actual[index]
-
end
-
end
-
-
1
def best_solution
-
@best_solution ||= pairings_maximizer.find_best_solution
-
end
-
-
1
def pairings_maximizer
-
@pairings_maximizer ||= begin
-
expected_matches = Hash[Array.new(expected.size) { |i| [i, []] }]
-
actual_matches = Hash[Array.new(actual.size) { |i| [i, []] }]
-
-
expected.each_with_index do |e, ei|
-
actual.each_with_index do |a, ai|
-
next unless values_match?(e, a)
-
-
expected_matches[ei] << ai
-
actual_matches[ai] << ei
-
end
-
end
-
-
PairingsMaximizer.new(expected_matches, actual_matches)
-
end
-
end
-
-
# Once we started supporting composing matchers, the algorithm for this matcher got
-
# much more complicated. Consider this expression:
-
#
-
# expect(["fool", "food"]).to contain_exactly(/foo/, /fool/)
-
#
-
# This should pass (because we can pair /fool/ with "fool" and /foo/ with "food"), but
-
# the original algorithm used by this matcher would pair the first elements it could
-
# (/foo/ with "fool"), which would leave /fool/ and "food" unmatched. When we have
-
# an expected element which is a matcher that matches a superset of actual items
-
# compared to another expected element matcher, we need to consider every possible pairing.
-
#
-
# This class is designed to maximize the number of actual/expected pairings -- or,
-
# conversely, to minimize the number of unpaired items. It's essentially a brute
-
# force solution, but with a few heuristics applied to reduce the size of the
-
# problem space:
-
#
-
# * Any items which match none of the items in the other list are immediately
-
# placed into the `unmatched_expected_indexes` or `unmatched_actual_indexes` array.
-
# The extra items and missing items in the matcher failure message are derived
-
# from these arrays.
-
# * Any items which reciprocally match only each other are paired up and not
-
# considered further.
-
#
-
# What's left is only the items which match multiple items from the other list
-
# (or vice versa). From here, it performs a brute-force depth-first search,
-
# looking for a solution which pairs all elements in both lists, or, barring that,
-
# that produces the fewest unmatched items.
-
#
-
# @private
-
1
class PairingsMaximizer
-
1
Solution = Struct.new(:unmatched_expected_indexes, :unmatched_actual_indexes,
-
:indeterminate_expected_indexes, :indeterminate_actual_indexes) do
-
1
def worse_than?(other)
-
unmatched_item_count > other.unmatched_item_count
-
end
-
-
1
def candidate?
-
indeterminate_expected_indexes.empty? &&
-
indeterminate_actual_indexes.empty?
-
end
-
-
1
def ideal?
-
candidate? && (
-
unmatched_expected_indexes.empty? ||
-
unmatched_actual_indexes.empty?
-
)
-
end
-
-
1
def unmatched_item_count
-
unmatched_expected_indexes.count + unmatched_actual_indexes.count
-
end
-
-
1
def +(derived_candidate_solution)
-
self.class.new(
-
unmatched_expected_indexes + derived_candidate_solution.unmatched_expected_indexes,
-
unmatched_actual_indexes + derived_candidate_solution.unmatched_actual_indexes,
-
# Ignore the indeterminate indexes: by the time we get here,
-
# we've dealt with all indeterminates.
-
[], []
-
)
-
end
-
end
-
-
1
attr_reader :expected_to_actual_matched_indexes, :actual_to_expected_matched_indexes, :solution
-
-
1
def initialize(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes)
-
@expected_to_actual_matched_indexes = expected_to_actual_matched_indexes
-
@actual_to_expected_matched_indexes = actual_to_expected_matched_indexes
-
-
unmatched_expected_indexes, indeterminate_expected_indexes =
-
categorize_indexes(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes)
-
-
unmatched_actual_indexes, indeterminate_actual_indexes =
-
categorize_indexes(actual_to_expected_matched_indexes, expected_to_actual_matched_indexes)
-
-
@solution = Solution.new(unmatched_expected_indexes, unmatched_actual_indexes,
-
indeterminate_expected_indexes, indeterminate_actual_indexes)
-
end
-
-
1
def find_best_solution
-
return solution if solution.candidate?
-
best_solution_so_far = NullSolution
-
-
expected_index = solution.indeterminate_expected_indexes.first
-
actuals = expected_to_actual_matched_indexes[expected_index]
-
-
actuals.each do |actual_index|
-
solution = best_solution_for_pairing(expected_index, actual_index)
-
return solution if solution.ideal?
-
best_solution_so_far = solution if best_solution_so_far.worse_than?(solution)
-
end
-
-
best_solution_so_far
-
end
-
-
1
private
-
-
# @private
-
# Starting solution that is worse than any other real solution.
-
1
NullSolution = Class.new do
-
1
def self.worse_than?(_other)
-
true
-
end
-
end
-
-
1
def categorize_indexes(indexes_to_categorize, other_indexes)
-
unmatched = []
-
indeterminate = []
-
-
indexes_to_categorize.each_pair do |index, matches|
-
if matches.empty?
-
unmatched << index
-
elsif !reciprocal_single_match?(matches, index, other_indexes)
-
indeterminate << index
-
end
-
end
-
-
return unmatched, indeterminate
-
end
-
-
1
def reciprocal_single_match?(matches, index, other_list)
-
return false unless matches.one?
-
other_list[matches.first] == [index]
-
end
-
-
1
def best_solution_for_pairing(expected_index, actual_index)
-
modified_expecteds = apply_pairing_to(
-
solution.indeterminate_expected_indexes,
-
expected_to_actual_matched_indexes, actual_index)
-
-
modified_expecteds.delete(expected_index)
-
-
modified_actuals = apply_pairing_to(
-
solution.indeterminate_actual_indexes,
-
actual_to_expected_matched_indexes, expected_index)
-
-
modified_actuals.delete(actual_index)
-
-
solution + self.class.new(modified_expecteds, modified_actuals).find_best_solution
-
end
-
-
1
def apply_pairing_to(indeterminates, original_matches, other_list_index)
-
indeterminates.inject({}) do |accum, index|
-
accum[index] = original_matches[index] - [other_list_index]
-
accum
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for `eq`.
-
# Not intended to be instantiated directly.
-
1
class Eq < BaseMatcher
-
# @api private
-
# @return [String]
-
1
def failure_message
-
"\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n"
-
end
-
-
# @api private
-
# @return [String]
-
1
def failure_message_when_negated
-
"\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n"
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"eq #{expected_formatted}"
-
end
-
-
# @api private
-
# @return [Boolean]
-
1
def diffable?
-
true
-
end
-
-
1
private
-
-
1
def match(expected, actual)
-
23
actual == expected
-
end
-
end
-
end
-
end
-
end
-
1
require 'rspec/support'
-
-
1
module RSpec
-
1
module Matchers
-
1
module BuiltIn
-
# @api private
-
# Provides the implementation for operator matchers.
-
# Not intended to be instantiated directly.
-
# Only available for use with `should`.
-
1
class OperatorMatcher
-
1
class << self
-
# @private
-
1
def registry
-
4
@registry ||= {}
-
end
-
-
# @private
-
1
def register(klass, operator, matcher)
-
2
registry[klass] ||= {}
-
2
registry[klass][operator] = matcher
-
end
-
-
# @private
-
1
def unregister(klass, operator)
-
registry[klass] && registry[klass].delete(operator)
-
end
-
-
# @private
-
1
def get(klass, operator)
-
klass.ancestors.each do |ancestor|
-
matcher = registry[ancestor] && registry[ancestor][operator]
-
return matcher if matcher
-
end
-
-
nil
-
end
-
end
-
-
1
register Enumerable, '=~', BuiltIn::ContainExactly
-
-
1
def initialize(actual)
-
@actual = actual
-
end
-
-
# @private
-
1
def self.use_custom_matcher_or_delegate(operator)
-
7
define_method(operator) do |expected|
-
if !has_non_generic_implementation_of?(operator) && (matcher = OperatorMatcher.get(@actual.class, operator))
-
@actual.__send__(::RSpec::Matchers.last_expectation_handler.should_method, matcher.new(expected))
-
else
-
eval_match(@actual, operator, expected)
-
end
-
end
-
-
7
negative_operator = operator.sub(/^=/, '!')
-
7
if negative_operator != operator && respond_to?(negative_operator)
-
2
define_method(negative_operator) do |_expected|
-
opposite_should = ::RSpec::Matchers.last_expectation_handler.opposite_should_method
-
raise "RSpec does not support `#{::RSpec::Matchers.last_expectation_handler.should_method} #{negative_operator} expected`. " \
-
"Use `#{opposite_should} #{operator} expected` instead."
-
end
-
end
-
end
-
-
1
['==', '===', '=~', '>', '>=', '<', '<='].each do |operator|
-
7
use_custom_matcher_or_delegate operator
-
end
-
-
# @private
-
1
def fail_with_message(message)
-
RSpec::Expectations.fail_with(message, @expected, @actual)
-
end
-
-
# @api private
-
# @return [String]
-
1
def description
-
"#{@operator} #{RSpec::Support::ObjectFormatter.format(@expected)}"
-
end
-
-
1
private
-
-
1
def has_non_generic_implementation_of?(op)
-
Support.method_handle_for(@actual, op).owner != ::Kernel
-
rescue NameError
-
false
-
end
-
-
1
def eval_match(actual, operator, expected)
-
::RSpec::Matchers.last_matcher = self
-
@operator, @expected = operator, expected
-
__delegate_operator(actual, operator, expected)
-
end
-
end
-
-
# @private
-
# Handles operator matcher for `should`.
-
1
class PositiveOperatorMatcher < OperatorMatcher
-
1
def __delegate_operator(actual, operator, expected)
-
if actual.__send__(operator, expected)
-
true
-
else
-
expected_formatted = RSpec::Support::ObjectFormatter.format(expected)
-
actual_formatted = RSpec::Support::ObjectFormatter.format(actual)
-
-
if ['==', '===', '=~'].include?(operator)
-
fail_with_message("expected: #{expected_formatted}\n got: #{actual_formatted} (using #{operator})")
-
else
-
fail_with_message("expected: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}")
-
end
-
end
-
end
-
end
-
-
# @private
-
# Handles operator matcher for `should_not`.
-
1
class NegativeOperatorMatcher < OperatorMatcher
-
1
def __delegate_operator(actual, operator, expected)
-
return false unless actual.__send__(operator, expected)
-
-
expected_formatted = RSpec::Support::ObjectFormatter.format(expected)
-
actual_formatted = RSpec::Support::ObjectFormatter.format(actual)
-
-
fail_with_message("expected not: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}")
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support "fuzzy_matcher"
-
-
1
module RSpec
-
1
module Matchers
-
# Mixin designed to support the composable matcher features
-
# of RSpec 3+. Mix it into your custom matcher classes to
-
# allow them to be used in a composable fashion.
-
#
-
# @api public
-
1
module Composable
-
# Creates a compound `and` expectation. The matcher will
-
# only pass if both sub-matchers pass.
-
# This can be chained together to form an arbitrarily long
-
# chain of matchers.
-
#
-
# @example
-
# expect(alphabet).to start_with("a").and end_with("z")
-
# expect(alphabet).to start_with("a") & end_with("z")
-
#
-
# @note The negative form (`expect(...).not_to matcher.and other`)
-
# is not supported at this time.
-
1
def and(matcher)
-
BuiltIn::Compound::And.new self, matcher
-
end
-
1
alias & and
-
-
# Creates a compound `or` expectation. The matcher will
-
# pass if either sub-matcher passes.
-
# This can be chained together to form an arbitrarily long
-
# chain of matchers.
-
#
-
# @example
-
# expect(stoplight.color).to eq("red").or eq("green").or eq("yellow")
-
# expect(stoplight.color).to eq("red") | eq("green") | eq("yellow")
-
#
-
# @note The negative form (`expect(...).not_to matcher.or other`)
-
# is not supported at this time.
-
1
def or(matcher)
-
BuiltIn::Compound::Or.new self, matcher
-
end
-
1
alias | or
-
-
# Delegates to `#matches?`. Allows matchers to be used in composable
-
# fashion and also supports using matchers in case statements.
-
1
def ===(value)
-
matches?(value)
-
end
-
-
1
private
-
-
# This provides a generic way to fuzzy-match an expected value against
-
# an actual value. It understands nested data structures (e.g. hashes
-
# and arrays) and is able to match against a matcher being used as
-
# the expected value or within the expected value at any level of
-
# nesting.
-
#
-
# Within a custom matcher you are encouraged to use this whenever your
-
# matcher needs to match two values, unless it needs more precise semantics.
-
# For example, the `eq` matcher _does not_ use this as it is meant to
-
# use `==` (and only `==`) for matching.
-
#
-
# @param expected [Object] what is expected
-
# @param actual [Object] the actual value
-
#
-
# @!visibility public
-
1
def values_match?(expected, actual)
-
expected = with_matchers_cloned(expected)
-
Support::FuzzyMatcher.values_match?(expected, actual)
-
end
-
-
# Returns the description of the given object in a way that is
-
# aware of composed matchers. If the object is a matcher with
-
# a `description` method, returns the description; otherwise
-
# returns `object.inspect`.
-
#
-
# You are encouraged to use this in your custom matcher's
-
# `description`, `failure_message` or
-
# `failure_message_when_negated` implementation if you are
-
# supporting matcher arguments.
-
#
-
# @!visibility public
-
1
def description_of(object)
-
RSpec::Support::ObjectFormatter.format(object)
-
end
-
-
# Transforms the given data structue (typically a hash or array)
-
# into a new data structure that, when `#inspect` is called on it,
-
# will provide descriptions of any contained matchers rather than
-
# the normal `#inspect` output.
-
#
-
# You are encouraged to use this in your custom matcher's
-
# `description`, `failure_message` or
-
# `failure_message_when_negated` implementation if you are
-
# supporting any arguments which may be a data structure
-
# containing matchers.
-
#
-
# @!visibility public
-
1
def surface_descriptions_in(item)
-
if Matchers.is_a_describable_matcher?(item)
-
DescribableItem.new(item)
-
elsif Hash === item
-
Hash[surface_descriptions_in(item.to_a)]
-
elsif Struct === item
-
RSpec::Support::ObjectFormatter.format(item)
-
elsif should_enumerate?(item)
-
begin
-
item.map { |subitem| surface_descriptions_in(subitem) }
-
rescue IOError # STDOUT is enumerable but `map` raises an error
-
RSpec::Support::ObjectFormatter.format(item)
-
end
-
else
-
item
-
end
-
end
-
-
# @private
-
# Historically, a single matcher instance was only checked
-
# against a single value. Given that the matcher was only
-
# used once, it's been common to memoize some intermediate
-
# calculation that is derived from the `actual` value in
-
# order to reuse that intermediate result in the failure
-
# message.
-
#
-
# This can cause a problem when using such a matcher as an
-
# argument to another matcher in a composed matcher expression,
-
# since the matcher instance may be checked against multiple
-
# values and produce invalid results due to the memoization.
-
#
-
# To deal with this, we clone any matchers in `expected` via
-
# this method when using `values_match?`, so that any memoization
-
# does not "leak" between checks.
-
1
def with_matchers_cloned(object)
-
if Matchers.is_a_matcher?(object)
-
object.clone
-
elsif Hash === object
-
Hash[with_matchers_cloned(object.to_a)]
-
elsif Struct === object
-
object
-
elsif should_enumerate?(object)
-
begin
-
object.map { |subobject| with_matchers_cloned(subobject) }
-
rescue IOError # STDOUT is enumerable but `map` raises an error
-
object
-
end
-
else
-
object
-
end
-
end
-
-
1
if String.ancestors.include?(Enumerable) # 1.8.7
-
# :nocov:
-
skipped
# Strings are not enumerable on 1.9, and on 1.8 they are an infinitely
-
skipped
# nested enumerable: since ruby lacks a character class, it yields
-
skipped
# 1-character strings, which are themselves enumerable, composed of a
-
skipped
# a single 1-character string, which is an enumerable, etc.
-
skipped
#
-
skipped
# @api private
-
skipped
def should_enumerate?(item)
-
skipped
return false if String === item
-
skipped
Enumerable === item && !(Range === item)
-
skipped
end
-
# :nocov:
-
else
-
# @api private
-
1
def should_enumerate?(item)
-
Enumerable === item && !(Range === item)
-
end
-
end
-
1
module_function :surface_descriptions_in, :should_enumerate?
-
-
# Wraps an item in order to surface its `description` via `inspect`.
-
# @api private
-
1
DescribableItem = Struct.new(:item) do
-
1
def inspect
-
"(#{item.description})"
-
end
-
-
1
def pretty_print(pp)
-
pp.text "(#{item.description})"
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# Defines the custom matcher DSL.
-
1
module DSL
-
# Defines a custom matcher.
-
# @see RSpec::Matchers
-
1
def define(name, &declarations)
-
warn_about_block_args(name, declarations)
-
define_method name do |*expected, &block_arg|
-
RSpec::Matchers::DSL::Matcher.new(name, declarations, self, *expected, &block_arg)
-
end
-
end
-
1
alias_method :matcher, :define
-
-
1
private
-
-
1
if Proc.method_defined?(:parameters)
-
1
def warn_about_block_args(name, declarations)
-
declarations.parameters.each do |type, arg_name|
-
next unless type == :block
-
RSpec.warning("Your `#{name}` custom matcher receives a block argument (`#{arg_name}`), " \
-
"but due to limitations in ruby, RSpec cannot provide the block. Instead, " \
-
"use the `block_arg` method to access the block")
-
end
-
end
-
else
-
# :nocov:
-
skipped
def warn_about_block_args(*)
-
skipped
# There's no way to detect block params on 1.8 since the method reflection APIs don't expose it
-
skipped
end
-
# :nocov:
-
end
-
-
2
RSpec.configure { |c| c.extend self } if RSpec.respond_to?(:configure)
-
-
# Contains the methods that are available from within the
-
# `RSpec::Matchers.define` DSL for creating custom matchers.
-
1
module Macros
-
# Stores the block that is used to determine whether this matcher passes
-
# or fails. The block should return a boolean value. When the matcher is
-
# passed to `expect(...).to` and the block returns `true`, then the expectation
-
# passes. Similarly, when the matcher is passed to `expect(...).not_to` and the
-
# block returns `false`, then the expectation passes.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :be_even do
-
# match do |actual|
-
# actual.even?
-
# end
-
# end
-
#
-
# expect(4).to be_even # passes
-
# expect(3).not_to be_even # passes
-
# expect(3).to be_even # fails
-
# expect(4).not_to be_even # fails
-
#
-
# @yield [Object] actual the actual value (i.e. the value wrapped by `expect`)
-
1
def match(&match_block)
-
define_user_override(:matches?, match_block) do |actual|
-
begin
-
@actual = actual
-
RSpec::Support.with_failure_notifier(RAISE_NOTIFIER) do
-
super(*actual_arg_for(match_block))
-
end
-
rescue RSpec::Expectations::ExpectationNotMetError
-
false
-
end
-
end
-
end
-
-
# @private
-
1
RAISE_NOTIFIER = Proc.new { |err, _opts| raise err }
-
-
# Use this to define the block for a negative expectation (`expect(...).not_to`)
-
# when the positive and negative forms require different handling. This
-
# is rarely necessary, but can be helpful, for example, when specifying
-
# asynchronous processes that require different timeouts.
-
#
-
# @yield [Object] actual the actual value (i.e. the value wrapped by `expect`)
-
1
def match_when_negated(&match_block)
-
define_user_override(:does_not_match?, match_block) do |actual|
-
@actual = actual
-
super(*actual_arg_for(match_block))
-
end
-
end
-
-
# Use this instead of `match` when the block will raise an exception
-
# rather than returning false to indicate a failure.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :accept_as_valid do |candidate_address|
-
# match_unless_raises ValidationException do |validator|
-
# validator.validate(candidate_address)
-
# end
-
# end
-
#
-
# expect(email_validator).to accept_as_valid("person@company.com")
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def match_unless_raises(expected_exception=Exception, &match_block)
-
define_user_override(:matches?, match_block) do |actual|
-
@actual = actual
-
begin
-
super(*actual_arg_for(match_block))
-
rescue expected_exception => @rescued_exception
-
false
-
else
-
true
-
end
-
end
-
end
-
-
# Customizes the failure messsage to use when this matcher is
-
# asked to positively match. Only use this when the message
-
# generated by default doesn't suit your needs.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :have_strength do |expected|
-
# match { your_match_logic }
-
#
-
# failure_message do |actual|
-
# "Expected strength of #{expected}, but had #{actual.strength}"
-
# end
-
# end
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def failure_message(&definition)
-
define_user_override(__method__, definition)
-
end
-
-
# Customize the failure messsage to use when this matcher is asked
-
# to negatively match. Only use this when the message generated by
-
# default doesn't suit your needs.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :have_strength do |expected|
-
# match { your_match_logic }
-
#
-
# failure_message_when_negated do |actual|
-
# "Expected not to have strength of #{expected}, but did"
-
# end
-
# end
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def failure_message_when_negated(&definition)
-
define_user_override(__method__, definition)
-
end
-
-
# Customize the description to use for one-liners. Only use this when
-
# the description generated by default doesn't suit your needs.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :qualify_for do |expected|
-
# match { your_match_logic }
-
#
-
# description do
-
# "qualify for #{expected}"
-
# end
-
# end
-
#
-
# @yield [Object] actual the actual object (i.e. the value wrapped by `expect`)
-
1
def description(&definition)
-
define_user_override(__method__, definition)
-
end
-
-
# Tells the matcher to diff the actual and expected values in the failure
-
# message.
-
1
def diffable
-
define_method(:diffable?) { true }
-
end
-
-
# Declares that the matcher can be used in a block expectation.
-
# Users will not be able to use your matcher in a block
-
# expectation without declaring this.
-
# (e.g. `expect { do_something }.to matcher`).
-
1
def supports_block_expectations
-
define_method(:supports_block_expectations?) { true }
-
end
-
-
# Convenience for defining methods on this matcher to create a fluent
-
# interface. The trick about fluent interfaces is that each method must
-
# return self in order to chain methods together. `chain` handles that
-
# for you. If the method is invoked and the
-
# `include_chain_clauses_in_custom_matcher_descriptions` config option
-
# hash been enabled, the chained method name and args will be added to the
-
# default description and failure message.
-
#
-
# In the common case where you just want the chained method to store some
-
# value(s) for later use (e.g. in `match`), you can provide one or more
-
# attribute names instead of a block; the chained method will store its
-
# arguments in instance variables with those names, and the values will
-
# be exposed via getters.
-
#
-
# @example
-
#
-
# RSpec::Matchers.define :have_errors_on do |key|
-
# chain :with do |message|
-
# @message = message
-
# end
-
#
-
# match do |actual|
-
# actual.errors[key] == @message
-
# end
-
# end
-
#
-
# expect(minor).to have_errors_on(:age).with("Not old enough to participate")
-
1
def chain(method_name, *attr_names, &definition)
-
unless block_given? ^ attr_names.any?
-
raise ArgumentError, "You must pass either a block or some attribute names (but not both) to `chain`."
-
end
-
-
definition = assign_attributes(attr_names) if attr_names.any?
-
-
define_user_override(method_name, definition) do |*args, &block|
-
super(*args, &block)
-
@chained_method_clauses.push([method_name, args])
-
self
-
end
-
end
-
-
1
def assign_attributes(attr_names)
-
attr_reader(*attr_names)
-
private(*attr_names)
-
-
lambda do |*attr_values|
-
attr_names.zip(attr_values) do |attr_name, attr_value|
-
instance_variable_set(:"@#{attr_name}", attr_value)
-
end
-
end
-
end
-
-
# assign_attributes isn't defined in the private section below because
-
# that makes MRI 1.9.2 emit a warning about private attributes.
-
1
private :assign_attributes
-
-
1
private
-
-
# Does the following:
-
#
-
# - Defines the named method using a user-provided block
-
# in @user_method_defs, which is included as an ancestor
-
# in the singleton class in which we eval the `define` block.
-
# - Defines an overriden definition for the same method
-
# usign the provided `our_def` block.
-
# - Provides a default `our_def` block for the common case
-
# of needing to call the user's definition with `@actual`
-
# as an arg, but only if their block's arity can handle it.
-
#
-
# This compiles the user block into an actual method, allowing
-
# them to use normal method constructs like `return`
-
# (e.g. for a early guard statement), while allowing us to define
-
# an override that can provide the wrapped handling
-
# (e.g. assigning `@actual`, rescueing errors, etc) and
-
# can `super` to the user's definition.
-
1
def define_user_override(method_name, user_def, &our_def)
-
@user_method_defs.__send__(:define_method, method_name, &user_def)
-
our_def ||= lambda { super(*actual_arg_for(user_def)) }
-
define_method(method_name, &our_def)
-
end
-
-
# Defines deprecated macro methods from RSpec 2 for backwards compatibility.
-
# @deprecated Use the methods from {Macros} instead.
-
1
module Deprecated
-
# @deprecated Use {Macros#match} instead.
-
1
def match_for_should(&definition)
-
RSpec.deprecate("`match_for_should`", :replacement => "`match`")
-
match(&definition)
-
end
-
-
# @deprecated Use {Macros#match_when_negated} instead.
-
1
def match_for_should_not(&definition)
-
RSpec.deprecate("`match_for_should_not`", :replacement => "`match_when_negated`")
-
match_when_negated(&definition)
-
end
-
-
# @deprecated Use {Macros#failure_message} instead.
-
1
def failure_message_for_should(&definition)
-
RSpec.deprecate("`failure_message_for_should`", :replacement => "`failure_message`")
-
failure_message(&definition)
-
end
-
-
# @deprecated Use {Macros#failure_message_when_negated} instead.
-
1
def failure_message_for_should_not(&definition)
-
RSpec.deprecate("`failure_message_for_should_not`", :replacement => "`failure_message_when_negated`")
-
failure_message_when_negated(&definition)
-
end
-
end
-
end
-
-
# Defines default implementations of the matcher
-
# protocol methods for custom matchers. You can
-
# override any of these using the {RSpec::Matchers::DSL::Macros Macros} methods
-
# from within an `RSpec::Matchers.define` block.
-
1
module DefaultImplementations
-
1
include BuiltIn::BaseMatcher::DefaultFailureMessages
-
-
# @api private
-
# Used internally by objects returns by `should` and `should_not`.
-
1
def diffable?
-
false
-
end
-
-
# The default description.
-
1
def description
-
english_name = EnglishPhrasing.split_words(name)
-
expected_list = EnglishPhrasing.list(expected)
-
"#{english_name}#{expected_list}#{chained_method_clause_sentences}"
-
end
-
-
# Matchers do not support block expectations by default. You
-
# must opt-in.
-
1
def supports_block_expectations?
-
false
-
end
-
-
# Most matchers do not expect call stack jumps.
-
1
def expects_call_stack_jump?
-
false
-
end
-
-
1
private
-
-
1
def chained_method_clause_sentences
-
return '' unless Expectations.configuration.include_chain_clauses_in_custom_matcher_descriptions?
-
-
@chained_method_clauses.map do |(method_name, method_args)|
-
english_name = EnglishPhrasing.split_words(method_name)
-
arg_list = EnglishPhrasing.list(method_args)
-
" #{english_name}#{arg_list}"
-
end.join
-
end
-
end
-
-
# The class used for custom matchers. The block passed to
-
# `RSpec::Matchers.define` will be evaluated in the context
-
# of the singleton class of an instance, and will have the
-
# {RSpec::Matchers::DSL::Macros Macros} methods available.
-
1
class Matcher
-
# Provides default implementations for the matcher protocol methods.
-
1
include DefaultImplementations
-
-
# Allows expectation expressions to be used in the match block.
-
1
include RSpec::Matchers
-
-
# Supports the matcher composability features of RSpec 3+.
-
1
include Composable
-
-
# Makes the macro methods available to an `RSpec::Matchers.define` block.
-
1
extend Macros
-
1
extend Macros::Deprecated
-
-
# Exposes the value being matched against -- generally the object
-
# object wrapped by `expect`.
-
1
attr_reader :actual
-
-
# Exposes the exception raised during the matching by `match_unless_raises`.
-
# Could be useful to extract details for a failure message.
-
1
attr_reader :rescued_exception
-
-
# The block parameter used in the expectation
-
1
attr_reader :block_arg
-
-
# The name of the matcher.
-
1
attr_reader :name
-
-
# @api private
-
1
def initialize(name, declarations, matcher_execution_context, *expected, &block_arg)
-
@name = name
-
@actual = nil
-
@expected_as_array = expected
-
@matcher_execution_context = matcher_execution_context
-
@chained_method_clauses = []
-
@block_arg = block_arg
-
-
class << self
-
# See `Macros#define_user_override` above, for an explanation.
-
include(@user_method_defs = Module.new)
-
self
-
end.class_exec(*expected, &declarations)
-
end
-
-
# Provides the expected value. This will return an array if
-
# multiple arguments were passed to the matcher; otherwise it
-
# will return a single value.
-
# @see #expected_as_array
-
1
def expected
-
if expected_as_array.size == 1
-
expected_as_array[0]
-
else
-
expected_as_array
-
end
-
end
-
-
# Returns the expected value as an an array. This exists primarily
-
# to aid in upgrading from RSpec 2.x, since in RSpec 2, `expected`
-
# always returned an array.
-
# @see #expected
-
1
attr_reader :expected_as_array
-
-
# Adds the name (rather than a cryptic hex number)
-
# so we can identify an instance of
-
# the matcher in error messages (e.g. for `NoMethodError`)
-
1
def inspect
-
"#<#{self.class.name} #{name}>"
-
end
-
-
1
if RUBY_VERSION.to_f >= 1.9
-
# Indicates that this matcher responds to messages
-
# from the `@matcher_execution_context` as well.
-
# Also, supports getting a method object for such methods.
-
1
def respond_to_missing?(method, include_private=false)
-
super || @matcher_execution_context.respond_to?(method, include_private)
-
end
-
else # for 1.8.7
-
# :nocov:
-
skipped
# Indicates that this matcher responds to messages
-
skipped
# from the `@matcher_execution_context` as well.
-
skipped
def respond_to?(method, include_private=false)
-
skipped
super || @matcher_execution_context.respond_to?(method, include_private)
-
skipped
end
-
# :nocov:
-
end
-
-
1
private
-
-
1
def actual_arg_for(block)
-
block.arity.zero? ? [] : [@actual]
-
end
-
-
# Takes care of forwarding unhandled messages to the
-
# `@matcher_execution_context` (typically the current
-
# running `RSpec::Core::Example`). This is needed by
-
# rspec-rails so that it can define matchers that wrap
-
# Rails' test helper methods, but it's also a useful
-
# feature in its own right.
-
1
def method_missing(method, *args, &block)
-
if @matcher_execution_context.respond_to?(method)
-
@matcher_execution_context.__send__ method, *args, &block
-
else
-
super(method, *args, &block)
-
end
-
end
-
end
-
end
-
end
-
end
-
-
1
RSpec::Matchers.extend RSpec::Matchers::DSL
-
1
module RSpec
-
1
module Matchers
-
# Facilitates converting ruby objects to English phrases.
-
1
module EnglishPhrasing
-
# Converts a symbol into an English expression.
-
#
-
# split_words(:banana_creme_pie) #=> "banana creme pie"
-
#
-
1
def self.split_words(sym)
-
sym.to_s.gsub(/_/, ' ')
-
end
-
-
# @note The returned string has a leading space except
-
# when given an empty list.
-
#
-
# Converts an object (often a collection of objects)
-
# into an English list.
-
#
-
# list(['banana', 'kiwi', 'mango'])
-
# #=> " \"banana\", \"kiwi\", and \"mango\""
-
#
-
# Given an empty collection, returns the empty string.
-
#
-
# list([]) #=> ""
-
#
-
1
def self.list(obj)
-
return " #{RSpec::Support::ObjectFormatter.format(obj)}" if !obj || Struct === obj
-
items = Array(obj).map { |w| RSpec::Support::ObjectFormatter.format(w) }
-
case items.length
-
when 0
-
""
-
when 1
-
" #{items[0]}"
-
when 2
-
" #{items[0]} and #{items[1]}"
-
else
-
" #{items[0...-1].join(', ')}, and #{items[-1]}"
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# @api private
-
# Handles list of expected values when there is a need to render
-
# multiple diffs. Also can handle one value.
-
1
class ExpectedsForMultipleDiffs
-
# @private
-
# Default diff label when there is only one matcher in diff
-
# output
-
1
DEFAULT_DIFF_LABEL = "Diff:".freeze
-
-
# @private
-
# Maximum readable matcher description length
-
1
DESCRIPTION_MAX_LENGTH = 65
-
-
1
def initialize(expected_list)
-
@expected_list = expected_list
-
end
-
-
# @api private
-
# Wraps provided expected value in instance of
-
# ExpectedForMultipleDiffs. If provided value is already an
-
# ExpectedForMultipleDiffs then it just returns it.
-
# @param [Any] expected value to be wrapped
-
# @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
-
1
def self.from(expected)
-
return expected if self === expected
-
new([[expected, DEFAULT_DIFF_LABEL]])
-
end
-
-
# @api private
-
# Wraps provided matcher list in instance of
-
# ExpectedForMultipleDiffs.
-
# @param [Array<Any>] matchers list of matchers to wrap
-
# @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
-
1
def self.for_many_matchers(matchers)
-
new(matchers.map { |m| [m.expected, diff_label_for(m)] })
-
end
-
-
# @api private
-
# Returns message with diff(s) appended for provided differ
-
# factory and actual value if there are any
-
# @param [String] message original failure message
-
# @param [Proc] differ
-
# @param [Any] actual value
-
# @return [String]
-
1
def message_with_diff(message, differ, actual)
-
diff = diffs(differ, actual)
-
message = "#{message}\n#{diff}" unless diff.empty?
-
message
-
end
-
-
1
private
-
-
1
def self.diff_label_for(matcher)
-
"Diff for (#{truncated(RSpec::Support::ObjectFormatter.format(matcher))}):"
-
end
-
-
1
def self.truncated(description)
-
return description if description.length <= DESCRIPTION_MAX_LENGTH
-
description[0...DESCRIPTION_MAX_LENGTH - 3] << "..."
-
end
-
-
1
def diffs(differ, actual)
-
@expected_list.map do |(expected, diff_label)|
-
diff = differ.diff(actual, expected)
-
next if diff.strip.empty?
-
"#{diff_label}#{diff}"
-
end.compact.join("\n")
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
1
class << self
-
# @private
-
1
attr_accessor :last_matcher, :last_expectation_handler
-
end
-
-
# @api private
-
# Used by rspec-core to clear the state used to generate
-
# descriptions after an example.
-
1
def self.clear_generated_description
-
7
self.last_matcher = nil
-
7
self.last_expectation_handler = nil
-
end
-
-
# @api private
-
# Generates an an example description based on the last expectation.
-
# Used by rspec-core's one-liner syntax.
-
1
def self.generated_description
-
return nil if last_expectation_handler.nil?
-
"#{last_expectation_handler.verb} #{last_description}"
-
end
-
-
1
private
-
-
1
def self.last_description
-
last_matcher.respond_to?(:description) ? last_matcher.description : <<-MESSAGE
-
When you call a matcher in an example without a String, like this:
-
-
specify { expect(object).to matcher }
-
-
or this:
-
-
it { is_expected.to matcher }
-
-
RSpec expects the matcher to have a #description method. You should either
-
add a String to the example this matcher is being used in, or give it a
-
description method. Then you won't have to suffer this lengthy warning again.
-
MESSAGE
-
end
-
end
-
end
-
1
module RSpec
-
1
module Matchers
-
# Provides the necessary plumbing to wrap a matcher with a decorator.
-
# @private
-
1
class MatcherDelegator
-
1
include Composable
-
1
attr_reader :base_matcher
-
-
1
def initialize(base_matcher)
-
@base_matcher = base_matcher
-
end
-
-
1
def method_missing(*args, &block)
-
base_matcher.__send__(*args, &block)
-
end
-
-
1
if ::RUBY_VERSION.to_f > 1.8
-
1
def respond_to_missing?(name, include_all=false)
-
super || base_matcher.respond_to?(name, include_all)
-
end
-
else
-
# :nocov:
-
skipped
def respond_to?(name, include_all=false)
-
skipped
super || base_matcher.respond_to?(name, include_all)
-
skipped
end
-
# :nocov:
-
end
-
-
1
def initialize_copy(other)
-
@base_matcher = @base_matcher.clone
-
super
-
end
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support 'caller_filter'
-
1
RSpec::Support.require_rspec_support 'warnings'
-
1
RSpec::Support.require_rspec_support 'ruby_features'
-
-
23
RSpec::Support.define_optimized_require_for_rspec(:mocks) { |f| require_relative f }
-
-
%w[
-
instance_method_stasher
-
method_double
-
argument_matchers
-
example_methods
-
proxy
-
test_double
-
argument_list_matcher
-
message_expectation
-
order_group
-
error_generator
-
space
-
mutate_const
-
targets
-
syntax
-
configuration
-
verifying_double
-
version
-
18
].each { |name| RSpec::Support.require_rspec_mocks name }
-
-
# Share the top-level RSpec namespace, because we are a core supported
-
# extension.
-
1
module RSpec
-
# Contains top-level utility methods. While this contains a few
-
# public methods, these are not generally meant to be called from
-
# a test or example. They exist primarily for integration with
-
# test frameworks (such as rspec-core).
-
1
module Mocks
-
# Performs per-test/example setup. This should be called before
-
# an test or example begins.
-
1
def self.setup
-
7
@space_stack << (@space = space.new_scope)
-
end
-
-
# Verifies any message expectations that were set during the
-
# test or example. This should be called at the end of an example.
-
1
def self.verify
-
7
space.verify_all
-
end
-
-
# Cleans up all test double state (including any methods that were
-
# redefined on partial doubles). This _must_ be called after
-
# each example, even if an error was raised during the example.
-
1
def self.teardown
-
7
space.reset_all
-
7
@space_stack.pop
-
7
@space = @space_stack.last || @root_space
-
end
-
-
# Adds an allowance (stub) on `subject`
-
#
-
# @param subject the subject to which the message will be added
-
# @param message a symbol, representing the message that will be
-
# added.
-
# @param opts a hash of options, :expected_from is used to set the
-
# original call site
-
# @yield an optional implementation for the allowance
-
#
-
# @example Defines the implementation of `foo` on `bar`, using the passed block
-
# x = 0
-
# RSpec::Mocks.allow_message(bar, :foo) { x += 1 }
-
1
def self.allow_message(subject, message, opts={}, &block)
-
space.proxy_for(subject).add_stub(message, opts, &block)
-
end
-
-
# Sets a message expectation on `subject`.
-
# @param subject the subject on which the message will be expected
-
# @param message a symbol, representing the message that will be
-
# expected.
-
# @param opts a hash of options, :expected_from is used to set the
-
# original call site
-
# @yield an optional implementation for the expectation
-
#
-
# @example Expect the message `foo` to receive `bar`, then call it
-
# RSpec::Mocks.expect_message(bar, :foo)
-
# bar.foo
-
1
def self.expect_message(subject, message, opts={}, &block)
-
space.proxy_for(subject).add_message_expectation(message, opts, &block)
-
end
-
-
# Call the passed block and verify mocks after it has executed. This allows
-
# mock usage in arbitrary places, such as a `before(:all)` hook.
-
1
def self.with_temporary_scope
-
setup
-
-
begin
-
yield
-
verify
-
ensure
-
teardown
-
end
-
end
-
-
1
class << self
-
# @private
-
1
attr_reader :space
-
end
-
1
@space_stack = []
-
1
@root_space = @space = RSpec::Mocks::RootSpace.new
-
-
# @private
-
1
IGNORED_BACKTRACE_LINE = 'this backtrace line is ignored'
-
-
# To speed up boot time a bit, delay loading optional or rarely
-
# used features until their first use.
-
1
autoload :AnyInstance, "rspec/mocks/any_instance"
-
1
autoload :ExpectChain, "rspec/mocks/message_chain"
-
1
autoload :StubChain, "rspec/mocks/message_chain"
-
1
autoload :MarshalExtension, "rspec/mocks/marshal_extension"
-
-
# Namespace for mock-related matchers.
-
1
module Matchers
-
1
autoload :HaveReceived, "rspec/mocks/matchers/have_received"
-
1
autoload :Receive, "rspec/mocks/matchers/receive"
-
1
autoload :ReceiveMessageChain, "rspec/mocks/matchers/receive_message_chain"
-
1
autoload :ReceiveMessages, "rspec/mocks/matchers/receive_messages"
-
end
-
end
-
end
-
# We intentionally do not use the `RSpec::Support.require...` methods
-
# here so that this file can be loaded individually, as documented
-
# below.
-
1
require 'rspec/mocks/argument_matchers'
-
1
require 'rspec/support/fuzzy_matcher'
-
-
1
module RSpec
-
1
module Mocks
-
# Wrapper for matching arguments against a list of expected values. Used by
-
# the `with` method on a `MessageExpectation`:
-
#
-
# expect(object).to receive(:message).with(:a, 'b', 3)
-
# object.message(:a, 'b', 3)
-
#
-
# Values passed to `with` can be literal values or argument matchers that
-
# match against the real objects .e.g.
-
#
-
# expect(object).to receive(:message).with(hash_including(:a => 'b'))
-
#
-
# Can also be used directly to match the contents of any `Array`. This
-
# enables 3rd party mocking libs to take advantage of rspec's argument
-
# matching without using the rest of rspec-mocks.
-
#
-
# require 'rspec/mocks/argument_list_matcher'
-
# include RSpec::Mocks::ArgumentMatchers
-
#
-
# arg_list_matcher = RSpec::Mocks::ArgumentListMatcher.new(123, hash_including(:a => 'b'))
-
# arg_list_matcher.args_match?(123, :a => 'b')
-
#
-
# This class is immutable.
-
#
-
# @see ArgumentMatchers
-
1
class ArgumentListMatcher
-
# @private
-
1
attr_reader :expected_args
-
-
# @api public
-
# @param [Array] expected_args a list of expected literals and/or argument matchers
-
#
-
# Initializes an `ArgumentListMatcher` with a collection of literal
-
# values and/or argument matchers.
-
#
-
# @see ArgumentMatchers
-
# @see #args_match?
-
1
def initialize(*expected_args)
-
1
@expected_args = expected_args
-
1
ensure_expected_args_valid!
-
end
-
-
# @api public
-
# @param [Array] args
-
#
-
# Matches each element in the `expected_args` against the element in the same
-
# position of the arguments passed to `new`.
-
#
-
# @see #initialize
-
1
def args_match?(*args)
-
Support::FuzzyMatcher.values_match?(resolve_expected_args_based_on(args), args)
-
end
-
-
# @private
-
# Resolves abstract arg placeholders like `no_args` and `any_args` into
-
# a more concrete arg list based on the provided `actual_args`.
-
1
def resolve_expected_args_based_on(actual_args)
-
return [] if [ArgumentMatchers::NoArgsMatcher::INSTANCE] == expected_args
-
-
any_args_index = expected_args.index { |a| ArgumentMatchers::AnyArgsMatcher::INSTANCE == a }
-
return expected_args unless any_args_index
-
-
replace_any_args_with_splat_of_anything(any_args_index, actual_args.count)
-
end
-
-
1
private
-
-
1
def replace_any_args_with_splat_of_anything(before_count, actual_args_count)
-
any_args_count = actual_args_count - expected_args.count + 1
-
after_count = expected_args.count - before_count - 1
-
-
any_args = 1.upto(any_args_count).map { ArgumentMatchers::AnyArgMatcher::INSTANCE }
-
expected_args.first(before_count) + any_args + expected_args.last(after_count)
-
end
-
-
1
def ensure_expected_args_valid!
-
2
if expected_args.count { |a| ArgumentMatchers::AnyArgsMatcher::INSTANCE == a } > 1
-
raise ArgumentError, "`any_args` can only be passed to " \
-
"`with` once but you have passed it multiple times."
-
1
elsif expected_args.count > 1 && expected_args.any? { |a| ArgumentMatchers::NoArgsMatcher::INSTANCE == a }
-
raise ArgumentError, "`no_args` can only be passed as a " \
-
"singleton argument to `with` (i.e. `with(no_args)`), " \
-
"but you have passed additional arguments."
-
end
-
end
-
-
# Value that will match all argument lists.
-
#
-
# @private
-
1
MATCH_ALL = new(ArgumentMatchers::AnyArgsMatcher::INSTANCE)
-
end
-
end
-
end
-
# This cannot take advantage of our relative requires, since this file is a
-
# dependency of `rspec/mocks/argument_list_matcher.rb`. See comment there for
-
# details.
-
1
require 'rspec/support/matcher_definition'
-
-
1
module RSpec
-
1
module Mocks
-
# ArgumentMatchers are placeholders that you can include in message
-
# expectations to match arguments against a broader check than simple
-
# equality.
-
#
-
# With the exception of `any_args` and `no_args`, they all match against
-
# the arg in same position in the argument list.
-
#
-
# @see ArgumentListMatcher
-
1
module ArgumentMatchers
-
# Acts like an arg splat, matching any number of args at any point in an arg list.
-
#
-
# @example
-
# expect(object).to receive(:message).with(1, 2, any_args)
-
#
-
# # matches any of these:
-
# object.message(1, 2)
-
# object.message(1, 2, 3)
-
# object.message(1, 2, 3, 4)
-
1
def any_args
-
AnyArgsMatcher::INSTANCE
-
end
-
-
# Matches any argument at all.
-
#
-
# @example
-
# expect(object).to receive(:message).with(anything)
-
1
def anything
-
AnyArgMatcher::INSTANCE
-
end
-
-
# Matches no arguments.
-
#
-
# @example
-
# expect(object).to receive(:message).with(no_args)
-
1
def no_args
-
NoArgsMatcher::INSTANCE
-
end
-
-
# Matches if the actual argument responds to the specified messages.
-
#
-
# @example
-
# expect(object).to receive(:message).with(duck_type(:hello))
-
# expect(object).to receive(:message).with(duck_type(:hello, :goodbye))
-
1
def duck_type(*args)
-
DuckTypeMatcher.new(*args)
-
end
-
-
# Matches a boolean value.
-
#
-
# @example
-
# expect(object).to receive(:message).with(boolean())
-
1
def boolean
-
BooleanMatcher::INSTANCE
-
end
-
-
# Matches a hash that includes the specified key(s) or key/value pairs.
-
# Ignores any additional keys.
-
#
-
# @example
-
# expect(object).to receive(:message).with(hash_including(:key => val))
-
# expect(object).to receive(:message).with(hash_including(:key))
-
# expect(object).to receive(:message).with(hash_including(:key, :key2 => val2))
-
1
def hash_including(*args)
-
HashIncludingMatcher.new(ArgumentMatchers.anythingize_lonely_keys(*args))
-
end
-
-
# Matches an array that includes the specified items at least once.
-
# Ignores duplicates and additional values
-
#
-
# @example
-
# expect(object).to receive(:message).with(array_including(1,2,3))
-
# expect(object).to receive(:message).with(array_including([1,2,3]))
-
1
def array_including(*args)
-
actually_an_array = Array === args.first && args.count == 1 ? args.first : args
-
ArrayIncludingMatcher.new(actually_an_array)
-
end
-
-
# Matches a hash that doesn't include the specified key(s) or key/value.
-
#
-
# @example
-
# expect(object).to receive(:message).with(hash_excluding(:key => val))
-
# expect(object).to receive(:message).with(hash_excluding(:key))
-
# expect(object).to receive(:message).with(hash_excluding(:key, :key2 => :val2))
-
1
def hash_excluding(*args)
-
HashExcludingMatcher.new(ArgumentMatchers.anythingize_lonely_keys(*args))
-
end
-
-
1
alias_method :hash_not_including, :hash_excluding
-
-
# Matches if `arg.instance_of?(klass)`
-
#
-
# @example
-
# expect(object).to receive(:message).with(instance_of(Thing))
-
1
def instance_of(klass)
-
InstanceOf.new(klass)
-
end
-
-
1
alias_method :an_instance_of, :instance_of
-
-
# Matches if `arg.kind_of?(klass)`
-
#
-
# @example
-
# expect(object).to receive(:message).with(kind_of(Thing))
-
1
def kind_of(klass)
-
KindOf.new(klass)
-
end
-
-
1
alias_method :a_kind_of, :kind_of
-
-
# @private
-
1
def self.anythingize_lonely_keys(*args)
-
hash = args.last.class == Hash ? args.delete_at(-1) : {}
-
args.each { | arg | hash[arg] = AnyArgMatcher::INSTANCE }
-
hash
-
end
-
-
# Intended to be subclassed by stateless, immutable argument matchers.
-
# Provides a `<klass name>::INSTANCE` constant for accessing a global
-
# singleton instance of the matcher. There is no need to construct
-
# multiple instance since there is no state. It also facilities the
-
# special case logic we need for some of these matchers, by making it
-
# easy to do comparisons like: `[klass::INSTANCE] == args` rather than
-
# `args.count == 1 && klass === args.first`.
-
#
-
# @private
-
1
class SingletonMatcher
-
1
private_class_method :new
-
-
1
def self.inherited(subklass)
-
4
subklass.const_set(:INSTANCE, subklass.send(:new))
-
end
-
end
-
-
# @private
-
1
class AnyArgsMatcher < SingletonMatcher
-
1
def description
-
"*(any args)"
-
end
-
end
-
-
# @private
-
1
class AnyArgMatcher < SingletonMatcher
-
1
def ===(_other)
-
true
-
end
-
-
1
def description
-
"anything"
-
end
-
end
-
-
# @private
-
1
class NoArgsMatcher < SingletonMatcher
-
1
def description
-
"no args"
-
end
-
end
-
-
# @private
-
1
class BooleanMatcher < SingletonMatcher
-
1
def ===(value)
-
true == value || false == value
-
end
-
-
1
def description
-
"boolean"
-
end
-
end
-
-
# @private
-
1
class BaseHashMatcher
-
1
def initialize(expected)
-
@expected = expected
-
end
-
-
1
def ===(predicate, actual)
-
@expected.__send__(predicate) do |k, v|
-
actual.key?(k) && Support::FuzzyMatcher.values_match?(v, actual[k])
-
end
-
rescue NoMethodError
-
false
-
end
-
-
1
def description(name)
-
"#{name}(#{formatted_expected_hash.inspect.sub(/^\{/, "").sub(/\}$/, "")})"
-
end
-
-
1
private
-
-
1
def formatted_expected_hash
-
Hash[
-
@expected.map do |k, v|
-
k = RSpec::Support.rspec_description_for_object(k)
-
v = RSpec::Support.rspec_description_for_object(v)
-
-
[k, v]
-
end
-
]
-
end
-
end
-
-
# @private
-
1
class HashIncludingMatcher < BaseHashMatcher
-
1
def ===(actual)
-
super(:all?, actual)
-
end
-
-
1
def description
-
super("hash_including")
-
end
-
end
-
-
# @private
-
1
class HashExcludingMatcher < BaseHashMatcher
-
1
def ===(actual)
-
super(:none?, actual)
-
end
-
-
1
def description
-
super("hash_not_including")
-
end
-
end
-
-
# @private
-
1
class ArrayIncludingMatcher
-
1
def initialize(expected)
-
@expected = expected
-
end
-
-
1
def ===(actual)
-
actual = actual.uniq
-
@expected.uniq.all? do |expected_element|
-
actual.any? do |actual_element|
-
RSpec::Support::FuzzyMatcher.values_match?(expected_element, actual_element)
-
end
-
end
-
end
-
-
1
def description
-
"array_including(#{formatted_expected_values})"
-
end
-
-
1
private
-
-
1
def formatted_expected_values
-
@expected.map do |x|
-
RSpec::Support.rspec_description_for_object(x)
-
end.join(", ")
-
end
-
end
-
-
# @private
-
1
class DuckTypeMatcher
-
1
def initialize(*methods_to_respond_to)
-
@methods_to_respond_to = methods_to_respond_to
-
end
-
-
1
def ===(value)
-
@methods_to_respond_to.all? { |message| value.respond_to?(message) }
-
end
-
-
1
def description
-
"duck_type(#{@methods_to_respond_to.map(&:inspect).join(', ')})"
-
end
-
end
-
-
# @private
-
1
class InstanceOf
-
1
def initialize(klass)
-
@klass = klass
-
end
-
-
1
def ===(actual)
-
actual.instance_of?(@klass)
-
end
-
-
1
def description
-
"an_instance_of(#{@klass.name})"
-
end
-
end
-
-
# @private
-
1
class KindOf
-
1
def initialize(klass)
-
@klass = klass
-
end
-
-
1
def ===(actual)
-
actual.kind_of?(@klass)
-
end
-
-
1
def description
-
"kind of #{@klass.name}"
-
end
-
end
-
-
1
matcher_namespace = name + '::'
-
1
::RSpec::Support.register_matcher_definition do |object|
-
# This is the best we have for now. We should tag all of our matchers
-
# with a module or something so we can test for it directly.
-
#
-
# (Note Module#parent in ActiveSupport is defined in a similar way.)
-
begin
-
object.class.name.include?(matcher_namespace)
-
rescue NoMethodError
-
# Some objects, like BasicObject, don't implemented standard
-
# reflection methods.
-
false
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support "object_formatter"
-
-
1
module RSpec
-
1
module Mocks
-
# Raised when a message expectation is not satisfied.
-
1
MockExpectationError = Class.new(Exception)
-
-
# Raised when a test double is used after it has been torn
-
# down (typically at the end of an rspec-core example).
-
1
ExpiredTestDoubleError = Class.new(MockExpectationError)
-
-
# Raised when doubles or partial doubles are used outside of the per-test lifecycle.
-
1
OutsideOfExampleError = Class.new(StandardError)
-
-
# Raised when an expectation customization method (e.g. `with`,
-
# `and_return`) is called on a message expectation which has already been
-
# invoked.
-
1
MockExpectationAlreadyInvokedError = Class.new(Exception)
-
-
# Raised for situations that RSpec cannot support due to mutations made
-
# externally on arguments that RSpec is holding onto to use for later
-
# comparisons.
-
#
-
# @deprecated We no longer raise this error but the constant remains until
-
# RSpec 4 for SemVer reasons.
-
1
CannotSupportArgMutationsError = Class.new(StandardError)
-
-
# @private
-
1
UnsupportedMatcherError = Class.new(StandardError)
-
# @private
-
1
NegationUnsupportedError = Class.new(StandardError)
-
# @private
-
1
VerifyingDoubleNotDefinedError = Class.new(StandardError)
-
-
# @private
-
1
class ErrorGenerator
-
1
attr_writer :opts
-
-
1
def initialize(target=nil)
-
@target = target
-
end
-
-
# @private
-
1
def opts
-
@opts ||= {}
-
end
-
-
# @private
-
1
def raise_unexpected_message_error(message, args)
-
__raise "#{intro} received unexpected message :#{message} with #{format_args(args)}"
-
end
-
-
# @private
-
1
def raise_unexpected_message_args_error(expectation, args_for_multiple_calls, source_id=nil)
-
__raise error_message(expectation, args_for_multiple_calls), nil, source_id
-
end
-
-
# @private
-
1
def raise_missing_default_stub_error(expectation, args_for_multiple_calls)
-
message = error_message(expectation, args_for_multiple_calls)
-
message << "\n Please stub a default value first if message might be received with other args as well. \n"
-
-
__raise message
-
end
-
-
# @private
-
1
def raise_similar_message_args_error(expectation, args_for_multiple_calls, backtrace_line=nil)
-
__raise error_message(expectation, args_for_multiple_calls), backtrace_line
-
end
-
-
1
def default_error_message(expectation, expected_args, actual_args)
-
[
-
intro,
-
"received",
-
expectation.message.inspect,
-
unexpected_arguments_message(expected_args, actual_args),
-
].join(" ")
-
end
-
-
# rubocop:disable Style/ParameterLists
-
# @private
-
1
def raise_expectation_error(message, expected_received_count, argument_list_matcher,
-
actual_received_count, expectation_count_type, args,
-
backtrace_line=nil, source_id=nil)
-
expected_part = expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher)
-
received_part = received_part_of_expectation_error(actual_received_count, args)
-
__raise "(#{intro(:unwrapped)}).#{message}#{format_args(args)}\n #{expected_part}\n #{received_part}", backtrace_line, source_id
-
end
-
# rubocop:enable Style/ParameterLists
-
-
# @private
-
1
def raise_unimplemented_error(doubled_module, method_name, object)
-
message = case object
-
when InstanceVerifyingDouble
-
"the %s class does not implement the instance method: %s" <<
-
if ObjectMethodReference.for(doubled_module, method_name).implemented?
-
". Perhaps you meant to use `class_double` instead?"
-
else
-
""
-
end
-
when ClassVerifyingDouble
-
"the %s class does not implement the class method: %s" <<
-
if InstanceMethodReference.for(doubled_module, method_name).implemented?
-
". Perhaps you meant to use `instance_double` instead?"
-
else
-
""
-
end
-
else
-
"%s does not implement: %s"
-
end
-
-
__raise message % [doubled_module.description, method_name]
-
end
-
-
# @private
-
1
def raise_non_public_error(method_name, visibility)
-
raise NoMethodError, "%s method `%s' called on %s" % [
-
visibility, method_name, intro
-
]
-
end
-
-
# @private
-
1
def raise_invalid_arguments_error(verifier)
-
__raise verifier.error_message
-
end
-
-
# @private
-
1
def raise_expired_test_double_error
-
raise ExpiredTestDoubleError,
-
"#{intro} was originally created in one example but has leaked into " \
-
"another example and can no longer be used. rspec-mocks' doubles are " \
-
"designed to only last for one example, and you need to create a new " \
-
"one in each example you wish to use it for."
-
end
-
-
# @private
-
1
def describe_expectation(verb, message, expected_received_count, _actual_received_count, args)
-
"#{verb} #{message}#{format_args(args)} #{count_message(expected_received_count)}"
-
end
-
-
# @private
-
1
def raise_out_of_order_error(message)
-
__raise "#{intro} received :#{message} out of order"
-
end
-
-
# @private
-
1
def raise_missing_block_error(args_to_yield)
-
__raise "#{intro} asked to yield |#{arg_list(args_to_yield)}| but no block was passed"
-
end
-
-
# @private
-
1
def raise_wrong_arity_error(args_to_yield, signature)
-
__raise "#{intro} yielded |#{arg_list(args_to_yield)}| to block with #{signature.description}"
-
end
-
-
# @private
-
1
def raise_only_valid_on_a_partial_double(method)
-
__raise "#{intro} is a pure test double. `#{method}` is only " \
-
"available on a partial double."
-
end
-
-
# @private
-
1
def raise_expectation_on_unstubbed_method(method)
-
__raise "#{intro} expected to have received #{method}, but that " \
-
"object is not a spy or method has not been stubbed."
-
end
-
-
# @private
-
1
def raise_expectation_on_mocked_method(method)
-
__raise "#{intro} expected to have received #{method}, but that " \
-
"method has been mocked instead of stubbed or spied."
-
end
-
-
# @private
-
1
def raise_double_negation_error(wrapped_expression)
-
__raise "Isn't life confusing enough? You've already set a " \
-
"negative message expectation and now you are trying to " \
-
"negate it again with `never`. What does an expression like " \
-
"`#{wrapped_expression}.not_to receive(:msg).never` even mean?"
-
end
-
-
# @private
-
1
def raise_verifying_double_not_defined_error(ref)
-
notify(VerifyingDoubleNotDefinedError.new(
-
"#{ref.description.inspect} is not a defined constant. " \
-
"Perhaps you misspelt it? " \
-
"Disable check with `verify_doubled_constant_names` configuration option."
-
))
-
end
-
-
# @private
-
1
def raise_have_received_disallowed(type, reason)
-
__raise "Using #{type}(...) with the `have_received` " \
-
"matcher is not supported#{reason}."
-
end
-
-
# @private
-
1
def raise_cant_constrain_count_for_negated_have_received_error(count_constraint)
-
__raise "can't use #{count_constraint} when negative"
-
end
-
-
# @private
-
1
def raise_method_not_stubbed_error(method_name)
-
__raise "The method `#{method_name}` was not stubbed or was already unstubbed"
-
end
-
-
# @private
-
1
def raise_already_invoked_error(message, calling_customization)
-
error_message = "The message expectation for #{intro}.#{message} has already been invoked " \
-
"and cannot be modified further (e.g. using `#{calling_customization}`). All message expectation " \
-
"customizations must be applied before it is used for the first time."
-
-
notify MockExpectationAlreadyInvokedError.new(error_message)
-
end
-
-
1
private
-
-
1
def received_part_of_expectation_error(actual_received_count, args)
-
"received: #{count_message(actual_received_count)}" +
-
method_call_args_description(args) do
-
actual_received_count > 0 && args.length > 0
-
end
-
end
-
-
1
def expected_part_of_expectation_error(expected_received_count, expectation_count_type, argument_list_matcher)
-
"expected: #{count_message(expected_received_count, expectation_count_type)}" +
-
method_call_args_description(argument_list_matcher.expected_args) do
-
argument_list_matcher.expected_args.length > 0
-
end
-
end
-
-
1
def method_call_args_description(args)
-
case args.first
-
when ArgumentMatchers::AnyArgsMatcher then " with any arguments"
-
when ArgumentMatchers::NoArgsMatcher then " with no arguments"
-
else
-
if yield
-
" with arguments: #{format_args(args)}"
-
else
-
""
-
end
-
end
-
end
-
-
1
def unexpected_arguments_message(expected_args_string, actual_args_string)
-
"with unexpected arguments\n expected: #{expected_args_string}\n got: #{actual_args_string}"
-
end
-
-
1
def error_message(expectation, args_for_multiple_calls)
-
expected_args = format_args(expectation.expected_args)
-
actual_args = format_received_args(args_for_multiple_calls)
-
message = default_error_message(expectation, expected_args, actual_args)
-
-
if args_for_multiple_calls.one?
-
diff = diff_message(expectation.expected_args, args_for_multiple_calls.first)
-
message << "\nDiff:#{diff}" unless diff.strip.empty?
-
end
-
-
message
-
end
-
-
1
def diff_message(expected_args, actual_args)
-
formatted_expected_args = expected_args.map do |x|
-
RSpec::Support.rspec_description_for_object(x)
-
end
-
-
formatted_expected_args, actual_args = unpack_string_args(formatted_expected_args, actual_args)
-
-
differ.diff(actual_args, formatted_expected_args)
-
end
-
-
1
def unpack_string_args(formatted_expected_args, actual_args)
-
if [formatted_expected_args, actual_args].all? { |x| list_of_exactly_one_string?(x) }
-
[formatted_expected_args.first, actual_args.first]
-
else
-
[formatted_expected_args, actual_args]
-
end
-
end
-
-
1
def list_of_exactly_one_string?(args)
-
Array === args && args.count == 1 && String === args.first
-
end
-
-
1
def differ
-
RSpec::Support::Differ.new(:color => RSpec::Mocks.configuration.color?)
-
end
-
-
1
def intro(unwrapped=false)
-
case @target
-
when TestDouble then TestDoubleFormatter.format(@target, unwrapped)
-
when Class then
-
formatted = "#{@target.inspect} (class)"
-
return formatted if unwrapped
-
"#<#{formatted}>"
-
when NilClass then "nil"
-
else @target
-
end
-
end
-
-
1
def __raise(message, backtrace_line=nil, source_id=nil)
-
message = opts[:message] unless opts[:message].nil?
-
exception = RSpec::Mocks::MockExpectationError.new(message)
-
prepend_to_backtrace(exception, backtrace_line) if backtrace_line
-
notify exception, :source_id => source_id
-
end
-
-
1
if RSpec::Support::Ruby.jruby?
-
def prepend_to_backtrace(exception, line)
-
raise exception
-
rescue RSpec::Mocks::MockExpectationError => with_backtrace
-
with_backtrace.backtrace.unshift(line)
-
end
-
else
-
1
def prepend_to_backtrace(exception, line)
-
exception.set_backtrace(caller.unshift line)
-
end
-
end
-
-
1
def notify(*args)
-
RSpec::Support.notify_failure(*args)
-
end
-
-
1
def format_args(args)
-
return "(no args)" if args.empty?
-
"(#{arg_list(args)})"
-
end
-
-
1
def arg_list(args)
-
args.map { |arg| RSpec::Support::ObjectFormatter.format(arg) }.join(", ")
-
end
-
-
1
def format_received_args(args_for_multiple_calls)
-
grouped_args(args_for_multiple_calls).map do |args_for_one_call, index|
-
"#{format_args(args_for_one_call)}#{group_count(index, args_for_multiple_calls)}"
-
end.join("\n ")
-
end
-
-
1
def count_message(count, expectation_count_type=nil)
-
return "at least #{times(count.abs)}" if count < 0 || expectation_count_type == :at_least
-
return "at most #{times(count)}" if expectation_count_type == :at_most
-
times(count)
-
end
-
-
1
def times(count)
-
"#{count} time#{count == 1 ? '' : 's'}"
-
end
-
-
1
def grouped_args(args)
-
Hash[args.group_by { |x| x }.map { |k, v| [k, v.count] }]
-
end
-
-
1
def group_count(index, args)
-
" (#{times(index)})" if args.size > 1 || index > 1
-
end
-
end
-
-
# @private
-
1
def self.error_generator
-
@error_generator ||= ErrorGenerator.new
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_mocks 'object_reference'
-
-
1
module RSpec
-
1
module Mocks
-
# Contains methods intended to be used from within code examples.
-
# Mix this in to your test context (such as a test framework base class)
-
# to use rspec-mocks with your test framework. If you're using rspec-core,
-
# it'll take care of doing this for you.
-
1
module ExampleMethods
-
1
include RSpec::Mocks::ArgumentMatchers
-
-
# @overload double()
-
# @overload double(name)
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload double(stubs)
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @overload double(name, stubs)
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @return (Double)
-
#
-
# Constructs an instance of [RSpec::Mocks::Double](RSpec::Mocks::Double) configured
-
# with an optional name, used for reporting in failure messages, and an optional
-
# hash of message/return-value pairs.
-
#
-
# @example
-
# book = double("book", :title => "The RSpec Book")
-
# book.title #=> "The RSpec Book"
-
#
-
# card = double("card", :suit => "Spades", :rank => "A")
-
# card.suit #=> "Spades"
-
# card.rank #=> "A"
-
#
-
1
def double(*args)
-
ExampleMethods.declare_double(Double, *args)
-
end
-
-
# @overload instance_double(doubled_class)
-
# @param doubled_class [String, Class]
-
# @overload instance_double(doubled_class, name)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload instance_double(doubled_class, stubs)
-
# @param doubled_class [String, Class]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload instance_double(doubled_class, name, stubs)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return InstanceVerifyingDouble
-
#
-
# Constructs a test double against a specific class. If the given class
-
# name has been loaded, only instance methods defined on the class are
-
# allowed to be stubbed. In all other ways it behaves like a
-
# [double](double).
-
1
def instance_double(doubled_class, *args)
-
ref = ObjectReference.for(doubled_class)
-
ExampleMethods.declare_verifying_double(InstanceVerifyingDouble, ref, *args)
-
end
-
-
# @overload class_double(doubled_class)
-
# @param doubled_class [String, Module]
-
# @overload class_double(doubled_class, name)
-
# @param doubled_class [String, Module]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload class_double(doubled_class, stubs)
-
# @param doubled_class [String, Module]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload class_double(doubled_class, name, stubs)
-
# @param doubled_class [String, Module]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ClassVerifyingDouble
-
#
-
# Constructs a test double against a specific class. If the given class
-
# name has been loaded, only class methods defined on the class are
-
# allowed to be stubbed. In all other ways it behaves like a
-
# [double](double).
-
1
def class_double(doubled_class, *args)
-
ref = ObjectReference.for(doubled_class)
-
ExampleMethods.declare_verifying_double(ClassVerifyingDouble, ref, *args)
-
end
-
-
# @overload object_double(object_or_name)
-
# @param object_or_name [String, Object]
-
# @overload object_double(object_or_name, name)
-
# @param object_or_name [String, Object]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload object_double(object_or_name, stubs)
-
# @param object_or_name [String, Object]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload object_double(object_or_name, name, stubs)
-
# @param object_or_name [String, Object]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ObjectVerifyingDouble
-
#
-
# Constructs a test double against a specific object. Only the methods
-
# the object responds to are allowed to be stubbed. If a String argument
-
# is provided, it is assumed to reference a constant object which is used
-
# for verification. In all other ways it behaves like a [double](double).
-
1
def object_double(object_or_name, *args)
-
ref = ObjectReference.for(object_or_name, :allow_direct_object_refs)
-
ExampleMethods.declare_verifying_double(ObjectVerifyingDouble, ref, *args)
-
end
-
-
# @overload spy()
-
# @overload spy(name)
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload spy(stubs)
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @overload spy(name, stubs)
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs (Hash) hash of message/return-value pairs
-
# @return (Double)
-
#
-
# Constructs a test double that is optimized for use with
-
# `have_received`. With a normal double one has to stub methods in order
-
# to be able to spy them. A spy automatically spies on all methods.
-
1
def spy(*args)
-
double(*args).as_null_object
-
end
-
-
# @overload instance_spy(doubled_class)
-
# @param doubled_class [String, Class]
-
# @overload instance_spy(doubled_class, name)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload instance_spy(doubled_class, stubs)
-
# @param doubled_class [String, Class]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload instance_spy(doubled_class, name, stubs)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return InstanceVerifyingDouble
-
#
-
# Constructs a test double that is optimized for use with `have_received`
-
# against a specific class. If the given class name has been loaded, only
-
# instance methods defined on the class are allowed to be stubbed. With
-
# a normal double one has to stub methods in order to be able to spy
-
# them. An instance_spy automatically spies on all instance methods to
-
# which the class responds.
-
1
def instance_spy(*args)
-
instance_double(*args).as_null_object
-
end
-
-
# @overload object_spy(object_or_name)
-
# @param object_or_name [String, Object]
-
# @overload object_spy(object_or_name, name)
-
# @param object_or_name [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload object_spy(object_or_name, stubs)
-
# @param object_or_name [String, Object]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload object_spy(object_or_name, name, stubs)
-
# @param object_or_name [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ObjectVerifyingDouble
-
#
-
# Constructs a test double that is optimized for use with `have_received`
-
# against a specific object. Only instance methods defined on the object
-
# are allowed to be stubbed. With a normal double one has to stub
-
# methods in order to be able to spy them. An object_spy automatically
-
# spies on all methods to which the object responds.
-
1
def object_spy(*args)
-
object_double(*args).as_null_object
-
end
-
-
# @overload class_spy(doubled_class)
-
# @param doubled_class [String, Module]
-
# @overload class_spy(doubled_class, name)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @overload class_spy(doubled_class, stubs)
-
# @param doubled_class [String, Module]
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @overload class_spy(doubled_class, name, stubs)
-
# @param doubled_class [String, Class]
-
# @param name [String/Symbol] name or description to be used in failure messages
-
# @param stubs [Hash] hash of message/return-value pairs
-
# @return ClassVerifyingDouble
-
#
-
# Constructs a test double that is optimized for use with `have_received`
-
# against a specific class. If the given class name has been loaded,
-
# only class methods defined on the class are allowed to be stubbed.
-
# With a normal double one has to stub methods in order to be able to spy
-
# them. An class_spy automatically spies on all class methods to which the
-
# class responds.
-
1
def class_spy(*args)
-
class_double(*args).as_null_object
-
end
-
-
# Disables warning messages about expectations being set on nil.
-
#
-
# By default warning messages are issued when expectations are set on
-
# nil. This is to prevent false-positives and to catch potential bugs
-
# early on.
-
1
def allow_message_expectations_on_nil
-
RSpec::Mocks.space.proxy_for(nil).warn_about_expectations = false
-
end
-
-
# Stubs the named constant with the given value.
-
# Like method stubs, the constant will be restored
-
# to its original value (or lack of one, if it was
-
# undefined) when the example completes.
-
#
-
# @param constant_name [String] The fully qualified name of the constant. The current
-
# constant scoping at the point of call is not considered.
-
# @param value [Object] The value to make the constant refer to. When the
-
# example completes, the constant will be restored to its prior state.
-
# @param options [Hash] Stubbing options.
-
# @option options :transfer_nested_constants [Boolean, Array<Symbol>] Determines
-
# what nested constants, if any, will be transferred from the original value
-
# of the constant to the new value of the constant. This only works if both
-
# the original and new values are modules (or classes).
-
# @return [Object] the stubbed value of the constant
-
#
-
# @example
-
# stub_const("MyClass", Class.new) # => Replaces (or defines) MyClass with a new class object.
-
# stub_const("SomeModel::PER_PAGE", 5) # => Sets SomeModel::PER_PAGE to 5.
-
#
-
# class CardDeck
-
# SUITS = [:Spades, :Diamonds, :Clubs, :Hearts]
-
# NUM_CARDS = 52
-
# end
-
#
-
# stub_const("CardDeck", Class.new)
-
# CardDeck::SUITS # => uninitialized constant error
-
# CardDeck::NUM_CARDS # => uninitialized constant error
-
#
-
# stub_const("CardDeck", Class.new, :transfer_nested_constants => true)
-
# CardDeck::SUITS # => our suits array
-
# CardDeck::NUM_CARDS # => 52
-
#
-
# stub_const("CardDeck", Class.new, :transfer_nested_constants => [:SUITS])
-
# CardDeck::SUITS # => our suits array
-
# CardDeck::NUM_CARDS # => uninitialized constant error
-
1
def stub_const(constant_name, value, options={})
-
ConstantMutator.stub(constant_name, value, options)
-
end
-
-
# Hides the named constant with the given value. The constant will be
-
# undefined for the duration of the test.
-
#
-
# Like method stubs, the constant will be restored to its original value
-
# when the example completes.
-
#
-
# @param constant_name [String] The fully qualified name of the constant.
-
# The current constant scoping at the point of call is not considered.
-
#
-
# @example
-
# hide_const("MyClass") # => MyClass is now an undefined constant
-
1
def hide_const(constant_name)
-
ConstantMutator.hide(constant_name)
-
end
-
-
# Verifies that the given object received the expected message during the
-
# course of the test. On a spy objects or as null object doubles this
-
# works for any method, on other objects the method must have
-
# been stubbed beforehand in order for messages to be verified.
-
#
-
# Stubbing and verifying messages received in this way implements the
-
# Test Spy pattern.
-
#
-
# @param method_name [Symbol] name of the method expected to have been
-
# called.
-
#
-
# @example
-
# invitation = double('invitation', accept: true)
-
# user.accept_invitation(invitation)
-
# expect(invitation).to have_received(:accept)
-
#
-
# # You can also use most message expectations:
-
# expect(invitation).to have_received(:accept).with(mailer).once
-
#
-
# @note `have_received(...).with(...)` is unable to work properly when
-
# passed arguments are mutated after the spy records the received message.
-
1
def have_received(method_name, &block)
-
Matchers::HaveReceived.new(method_name, &block)
-
end
-
-
# @method expect
-
# Used to wrap an object in preparation for setting a mock expectation
-
# on it.
-
#
-
# @example
-
# expect(obj).to receive(:foo).with(5).and_return(:return_value)
-
#
-
# @note This method is usually provided by rspec-expectations. However,
-
# if you use rspec-mocks without rspec-expectations, there's a definition
-
# of it that is made available here. If you disable the `:expect` syntax
-
# this method will be undefined.
-
-
# @method allow
-
# Used to wrap an object in preparation for stubbing a method
-
# on it.
-
#
-
# @example
-
# allow(dbl).to receive(:foo).with(5).and_return(:return_value)
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method expect_any_instance_of
-
# Used to wrap a class in preparation for setting a mock expectation
-
# on instances of it.
-
#
-
# @example
-
# expect_any_instance_of(MyClass).to receive(:foo)
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method allow_any_instance_of
-
# Used to wrap a class in preparation for stubbing a method
-
# on instances of it.
-
#
-
# @example
-
# allow_any_instance_of(MyClass).to receive(:foo)
-
#
-
# @note This is only available when you have enabled the `expect` syntax.
-
-
# @method receive
-
# Used to specify a message that you expect or allow an object
-
# to receive. The object returned by `receive` supports the same
-
# fluent interface that `should_receive` and `stub` have always
-
# supported, allowing you to constrain the arguments or number of
-
# times, and configure how the object should respond to the message.
-
#
-
# @example
-
# expect(obj).to receive(:hello).with("world").exactly(3).times
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method receive_messages
-
# Shorthand syntax used to setup message(s), and their return value(s),
-
# that you expect or allow an object to receive. The method takes a hash
-
# of messages and their respective return values. Unlike with `receive`,
-
# you cannot apply further customizations using a block or the fluent
-
# interface.
-
#
-
# @example
-
# allow(obj).to receive_messages(:speak => "Hello World")
-
# allow(obj).to receive_messages(:speak => "Hello", :meow => "Meow")
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @method receive_message_chain
-
# @overload receive_message_chain(method1, method2)
-
# @overload receive_message_chain("method1.method2")
-
# @overload receive_message_chain(method1, method_to_value_hash)
-
#
-
# stubs/mocks a chain of messages on an object or test double.
-
#
-
# ## Warning:
-
#
-
# Chains can be arbitrarily long, which makes it quite painless to
-
# violate the Law of Demeter in violent ways, so you should consider any
-
# use of `receive_message_chain` a code smell. Even though not all code smells
-
# indicate real problems (think fluent interfaces), `receive_message_chain` still
-
# results in brittle examples. For example, if you write
-
# `allow(foo).to receive_message_chain(:bar, :baz => 37)` in a spec and then the
-
# implementation calls `foo.baz.bar`, the stub will not work.
-
#
-
# @example
-
# allow(double).to receive_message_chain("foo.bar") { :baz }
-
# allow(double).to receive_message_chain(:foo, :bar => :baz)
-
# allow(double).to receive_message_chain(:foo, :bar) { :baz }
-
#
-
# # Given any of ^^ these three forms ^^:
-
# double.foo.bar # => :baz
-
#
-
# # Common use in Rails/ActiveRecord:
-
# allow(Article).to receive_message_chain("recent.published") { [Article.new] }
-
#
-
# @note If you disable the `:expect` syntax this method will be undefined.
-
-
# @private
-
1
def self.included(klass)
-
1
klass.class_exec do
-
# This gets mixed in so that if `RSpec::Matchers` is included in
-
# `klass` later, it's definition of `expect` will take precedence.
-
1
include ExpectHost unless method_defined?(:expect)
-
end
-
end
-
-
# @private
-
1
def self.extended(object)
-
# This gets extended in so that if `RSpec::Matchers` is included in
-
# `klass` later, it's definition of `expect` will take precedence.
-
object.extend ExpectHost unless object.respond_to?(:expect)
-
end
-
-
# @private
-
1
def self.declare_verifying_double(type, ref, *args)
-
if RSpec::Mocks.configuration.verify_doubled_constant_names? &&
-
!ref.defined?
-
-
RSpec::Mocks.error_generator.raise_verifying_double_not_defined_error(ref)
-
end
-
-
RSpec::Mocks.configuration.verifying_double_callbacks.each do |block|
-
block.call(ref)
-
end
-
-
declare_double(type, ref, *args)
-
end
-
-
# @private
-
1
def self.declare_double(type, *args)
-
args << {} unless Hash === args.last
-
type.new(*args)
-
end
-
-
# This module exists to host the `expect` method for cases where
-
# rspec-mocks is used w/o rspec-expectations.
-
1
module ExpectHost
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class InstanceMethodStasher
-
1
def initialize(object, method)
-
@object = object
-
@method = method
-
@klass = (class << object; self; end)
-
-
@original_method = nil
-
@method_is_stashed = false
-
end
-
-
1
attr_reader :original_method
-
-
1
if RUBY_VERSION.to_f < 1.9
-
# @private
-
def method_is_stashed?
-
@method_is_stashed
-
end
-
-
# @private
-
def stash
-
return if !method_defined_directly_on_klass? || @method_is_stashed
-
-
@klass.__send__(:alias_method, stashed_method_name, @method)
-
@method_is_stashed = true
-
end
-
-
# @private
-
def stashed_method_name
-
"obfuscated_by_rspec_mocks__#{@method}"
-
end
-
private :stashed_method_name
-
-
# @private
-
def restore
-
return unless @method_is_stashed
-
-
if @klass.__send__(:method_defined?, @method)
-
@klass.__send__(:undef_method, @method)
-
end
-
@klass.__send__(:alias_method, @method, stashed_method_name)
-
@klass.__send__(:remove_method, stashed_method_name)
-
@method_is_stashed = false
-
end
-
else
-
-
# @private
-
1
def method_is_stashed?
-
!!@original_method
-
end
-
-
# @private
-
1
def stash
-
return unless method_defined_directly_on_klass?
-
@original_method ||= ::RSpec::Support.method_handle_for(@object, @method)
-
end
-
-
# @private
-
1
def restore
-
return unless @original_method
-
-
if @klass.__send__(:method_defined?, @method)
-
@klass.__send__(:undef_method, @method)
-
end
-
-
handle_restoration_failures do
-
@klass.__send__(:define_method, @method, @original_method)
-
end
-
-
@original_method = nil
-
end
-
end
-
-
1
if RUBY_DESCRIPTION.include?('2.0.0p247') || RUBY_DESCRIPTION.include?('2.0.0p195')
-
# ruby 2.0.0-p247 and 2.0.0-p195 both have a bug that we can't work around :(.
-
# https://bugs.ruby-lang.org/issues/8686
-
def handle_restoration_failures
-
yield
-
rescue TypeError
-
RSpec.warn_with(
-
"RSpec failed to properly restore a partial double (#{@object.inspect}) " \
-
"to its original state due to a known bug in MRI 2.0.0-p195 & p247 " \
-
"(https://bugs.ruby-lang.org/issues/8686). This object may remain " \
-
"screwed up for the rest of this process. Please upgrade to 2.0.0-p353 or above.",
-
:call_site => nil, :use_spec_location_as_call_site => true
-
)
-
end
-
else
-
1
def handle_restoration_failures
-
# No known reasons for restoration to fail on other rubies.
-
yield
-
end
-
end
-
-
1
private
-
-
# @private
-
1
def method_defined_directly_on_klass?
-
method_defined_on_klass? && method_owned_by_klass?
-
end
-
-
# @private
-
1
def method_defined_on_klass?(klass=@klass)
-
MethodReference.method_defined_at_any_visibility?(klass, @method)
-
end
-
-
1
def method_owned_by_klass?
-
owner = @klass.instance_method(@method).owner
-
-
# On Ruby 2.0.0+ the owner of a method on a class which has been
-
# `prepend`ed may actually be an instance, e.g.
-
# `#<MyClass:0x007fbb94e3cd10>`, rather than the expected `MyClass`.
-
owner = owner.class unless Module === owner
-
-
# On some 1.9s (e.g. rubinius) aliased methods
-
# can report the wrong owner. Example:
-
# class MyClass
-
# class << self
-
# alias alternate_new new
-
# end
-
# end
-
#
-
# MyClass.owner(:alternate_new) returns `Class` when incorrect,
-
# but we need to consider the owner to be `MyClass` because
-
# it is not actually available on `Class` but is on `MyClass`.
-
# Hence, we verify that the owner actually has the method defined.
-
# If the given owner does not have the method defined, we assume
-
# that the method is actually owned by @klass.
-
owner == @klass || !(method_defined_on_klass?(owner))
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# A message expectation that only allows concrete return values to be set
-
# for a message. While this same effect can be achieved using a standard
-
# MessageExpecation, this version is much faster and so can be used as an
-
# optimization.
-
#
-
# @private
-
1
class SimpleMessageExpectation
-
1
def initialize(message, response, error_generator, backtrace_line=nil)
-
@message, @response, @error_generator, @backtrace_line = message.to_sym, response, error_generator, backtrace_line
-
@received = false
-
end
-
-
1
def invoke(*_)
-
@received = true
-
@response
-
end
-
-
1
def matches?(message, *_)
-
@message == message.to_sym
-
end
-
-
1
def called_max_times?
-
false
-
end
-
-
1
def verify_messages_received
-
return if @received
-
@error_generator.raise_expectation_error(
-
@message, 1, ArgumentListMatcher::MATCH_ALL, 0, nil, [], @backtrace_line
-
)
-
end
-
-
1
def unadvise(_)
-
end
-
end
-
-
# Represents an individual method stub or message expectation. The methods
-
# defined here can be used to configure how it behaves. The methods return
-
# `self` so that they can be chained together to form a fluent interface.
-
1
class MessageExpectation
-
# @!group Configuring Responses
-
-
# @overload and_return(value)
-
# @overload and_return(first_value, second_value)
-
#
-
# Tells the object to return a value when it receives the message. Given
-
# more than one value, the first value is returned the first time the
-
# message is received, the second value is returned the next time, etc,
-
# etc.
-
#
-
# If the message is received more times than there are values, the last
-
# value is received for every subsequent call.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @example
-
# allow(counter).to receive(:count).and_return(1)
-
# counter.count # => 1
-
# counter.count # => 1
-
#
-
# allow(counter).to receive(:count).and_return(1,2,3)
-
# counter.count # => 1
-
# counter.count # => 2
-
# counter.count # => 3
-
# counter.count # => 3
-
# counter.count # => 3
-
# # etc
-
1
def and_return(first_value, *values)
-
raise_already_invoked_error_if_necessary(__method__)
-
if negative?
-
raise "`and_return` is not supported with negative message expectations"
-
end
-
-
if block_given?
-
raise ArgumentError, "Implementation blocks aren't supported with `and_return`"
-
end
-
-
values.unshift(first_value)
-
@expected_received_count = [@expected_received_count, values.size].max unless ignoring_args? || (@expected_received_count == 0 && @at_least)
-
self.terminal_implementation_action = AndReturnImplementation.new(values)
-
-
nil
-
end
-
-
# Tells the object to delegate to the original unmodified method
-
# when it receives the message.
-
#
-
# @note This is only available on partial doubles.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @example
-
# expect(counter).to receive(:increment).and_call_original
-
# original_count = counter.count
-
# counter.increment
-
# expect(counter.count).to eq(original_count + 1)
-
1
def and_call_original
-
and_wrap_original do |original, *args, &block|
-
original.call(*args, &block)
-
end
-
end
-
-
# Decorates the stubbed method with the supplied block. The original
-
# unmodified method is passed to the block along with any method call
-
# arguments so you can delegate to it, whilst still being able to
-
# change what args are passed to it and/or change the return value.
-
#
-
# @note This is only available on partial doubles.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @example
-
# expect(api).to receive(:large_list).and_wrap_original do |original_method, *args, &block|
-
# original_method.call(*args, &block).first(10)
-
# end
-
1
def and_wrap_original(&block)
-
if RSpec::Mocks::TestDouble === @method_double.object
-
@error_generator.raise_only_valid_on_a_partial_double(:and_call_original)
-
else
-
warn_about_stub_override if implementation.inner_action
-
@implementation = AndWrapOriginalImplementation.new(@method_double.original_implementation_callable, block)
-
@yield_receiver_to_implementation_block = false
-
end
-
-
nil
-
end
-
-
# @overload and_raise
-
# @overload and_raise(ExceptionClass)
-
# @overload and_raise(ExceptionClass, message)
-
# @overload and_raise(exception_instance)
-
#
-
# Tells the object to raise an exception when the message is received.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @note
-
# When you pass an exception class, the MessageExpectation will raise
-
# an instance of it, creating it with `exception` and passing `message`
-
# if specified. If the exception class initializer requires more than
-
# one parameters, you must pass in an instance and not the class,
-
# otherwise this method will raise an ArgumentError exception.
-
#
-
# @example
-
# allow(car).to receive(:go).and_raise
-
# allow(car).to receive(:go).and_raise(OutOfGas)
-
# allow(car).to receive(:go).and_raise(OutOfGas, "At least 2 oz of gas needed to drive")
-
# allow(car).to receive(:go).and_raise(OutOfGas.new(2, :oz))
-
1
def and_raise(*args)
-
raise_already_invoked_error_if_necessary(__method__)
-
self.terminal_implementation_action = Proc.new { raise(*args) }
-
nil
-
end
-
-
# @overload and_throw(symbol)
-
# @overload and_throw(symbol, object)
-
#
-
# Tells the object to throw a symbol (with the object if that form is
-
# used) when the message is received.
-
#
-
# @return [nil] No further chaining is supported after this.
-
# @example
-
# allow(car).to receive(:go).and_throw(:out_of_gas)
-
# allow(car).to receive(:go).and_throw(:out_of_gas, :level => 0.1)
-
1
def and_throw(*args)
-
raise_already_invoked_error_if_necessary(__method__)
-
self.terminal_implementation_action = Proc.new { throw(*args) }
-
nil
-
end
-
-
# Tells the object to yield one or more args to a block when the message
-
# is received.
-
#
-
# @return [MessageExpecation] self, to support further chaining.
-
# @example
-
# stream.stub(:open).and_yield(StringIO.new)
-
1
def and_yield(*args, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
yield @eval_context = Object.new if block
-
-
# Initialize args to yield now that it's being used, see also: comment
-
# in constructor.
-
@args_to_yield ||= []
-
-
@args_to_yield << args
-
self.initial_implementation_action = AndYieldImplementation.new(@args_to_yield, @eval_context, @error_generator)
-
self
-
end
-
# @!endgroup
-
-
# @!group Constraining Receive Counts
-
-
# Constrain a message expectation to be received a specific number of
-
# times.
-
#
-
# @return [MessageExpecation] self, to support further chaining.
-
# @example
-
# expect(dealer).to receive(:deal_card).exactly(10).times
-
1
def exactly(n, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, n
-
self
-
end
-
-
# Constrain a message expectation to be received at least a specific
-
# number of times.
-
#
-
# @return [MessageExpecation] self, to support further chaining.
-
# @example
-
# expect(dealer).to receive(:deal_card).at_least(9).times
-
1
def at_least(n, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
set_expected_received_count :at_least, n
-
-
if n == 0
-
raise "at_least(0) has been removed, use allow(...).to receive(:message) instead"
-
end
-
-
self.inner_implementation_action = block
-
-
self
-
end
-
-
# Constrain a message expectation to be received at most a specific
-
# number of times.
-
#
-
# @return [MessageExpecation] self, to support further chaining.
-
# @example
-
# expect(dealer).to receive(:deal_card).at_most(10).times
-
1
def at_most(n, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
self.inner_implementation_action = block
-
set_expected_received_count :at_most, n
-
self
-
end
-
-
# Syntactic sugar for `exactly`, `at_least` and `at_most`
-
#
-
# @return [MessageExpecation] self, to support further chaining.
-
# @example
-
# expect(dealer).to receive(:deal_card).exactly(10).times
-
# expect(dealer).to receive(:deal_card).at_least(10).times
-
# expect(dealer).to receive(:deal_card).at_most(10).times
-
1
def times(&block)
-
self.inner_implementation_action = block
-
self
-
end
-
-
# Expect a message not to be received at all.
-
#
-
# @return [MessageExpecation] self, to support further chaining.
-
# @example
-
# expect(car).to receive(:stop).never
-
1
def never
-
error_generator.raise_double_negation_error("expect(obj)") if negative?
-
@expected_received_count = 0
-
self
-
end
-
-
# Expect a message to be received exactly one time.
-
#
-
# @return [MessageExpecation] self, to support further chaining.
-
# @example
-
# expect(car).to receive(:go).once
-
1
def once(&block)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, 1
-
self
-
end
-
-
# Expect a message to be received exactly two times.
-
#
-
# @return [MessageExpecation] self, to support further chaining.
-
# @example
-
# expect(car).to receive(:go).twice
-
1
def twice(&block)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, 2
-
self
-
end
-
-
# Expect a message to be received exactly three times.
-
#
-
# @return [MessageExpecation] self, to support further chaining.
-
# @example
-
# expect(car).to receive(:go).thrice
-
1
def thrice(&block)
-
self.inner_implementation_action = block
-
set_expected_received_count :exactly, 3
-
self
-
end
-
# @!endgroup
-
-
# @!group Other Constraints
-
-
# Constrains a stub or message expectation to invocations with specific
-
# arguments.
-
#
-
# With a stub, if the message might be received with other args as well,
-
# you should stub a default value first, and then stub or mock the same
-
# message using `with` to constrain to specific arguments.
-
#
-
# A message expectation will fail if the message is received with different
-
# arguments.
-
#
-
# @return [MessageExpecation] self, to support further chaining.
-
# @example
-
# allow(cart).to receive(:add) { :failure }
-
# allow(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success }
-
# cart.add(Book.new(:isbn => 1234567890))
-
# # => :failure
-
# cart.add(Book.new(:isbn => 1934356379))
-
# # => :success
-
#
-
# expect(cart).to receive(:add).with(Book.new(:isbn => 1934356379)) { :success }
-
# cart.add(Book.new(:isbn => 1234567890))
-
# # => failed expectation
-
# cart.add(Book.new(:isbn => 1934356379))
-
# # => passes
-
1
def with(*args, &block)
-
raise_already_invoked_error_if_necessary(__method__)
-
if args.empty?
-
raise ArgumentError,
-
"`with` must have at least one argument. Use `no_args` matcher to set the expectation of receiving no arguments."
-
end
-
-
self.inner_implementation_action = block
-
@argument_list_matcher = ArgumentListMatcher.new(*args)
-
self
-
end
-
-
# Expect messages to be received in a specific order.
-
#
-
# @return [MessageExpecation] self, to support further chaining.
-
# @example
-
# expect(api).to receive(:prepare).ordered
-
# expect(api).to receive(:run).ordered
-
# expect(api).to receive(:finish).ordered
-
1
def ordered(&block)
-
self.inner_implementation_action = block
-
additional_expected_calls.times do
-
@order_group.register(self)
-
end
-
@ordered = true
-
self
-
end
-
-
# @private
-
# Contains the parts of `MessageExpecation` that aren't part of
-
# rspec-mocks' public API. The class is very big and could really use
-
# some collaborators it delegates to for this stuff but for now this was
-
# the simplest way to split the public from private stuff to make it
-
# easier to publish the docs for the APIs we want published.
-
1
module ImplementationDetails
-
1
attr_accessor :error_generator, :implementation
-
1
attr_reader :message
-
1
attr_reader :orig_object
-
1
attr_writer :expected_received_count, :expected_from, :argument_list_matcher
-
1
protected :expected_received_count=, :expected_from=, :error_generator, :error_generator=, :implementation=
-
-
# rubocop:disable Style/ParameterLists
-
1
def initialize(error_generator, expectation_ordering, expected_from, method_double,
-
type=:expectation, opts={}, &implementation_block)
-
@error_generator = error_generator
-
@error_generator.opts = opts
-
@expected_from = expected_from
-
@method_double = method_double
-
@orig_object = @method_double.object
-
@message = @method_double.method_name
-
@actual_received_count = 0
-
@expected_received_count = type == :expectation ? 1 : :any
-
@argument_list_matcher = ArgumentListMatcher::MATCH_ALL
-
@order_group = expectation_ordering
-
@order_group.register(self) unless type == :stub
-
@expectation_type = type
-
@ordered = false
-
@at_least = @at_most = @exactly = nil
-
-
# Initialized to nil so that we don't allocate an array for every
-
# mock or stub. See also comment in `and_yield`.
-
@args_to_yield = nil
-
@eval_context = nil
-
@yield_receiver_to_implementation_block = false
-
-
@implementation = Implementation.new
-
self.inner_implementation_action = implementation_block
-
end
-
# rubocop:enable Style/ParameterLists
-
-
1
def expected_args
-
@argument_list_matcher.expected_args
-
end
-
-
1
def and_yield_receiver_to_implementation
-
@yield_receiver_to_implementation_block = true
-
self
-
end
-
-
1
def yield_receiver_to_implementation_block?
-
@yield_receiver_to_implementation_block
-
end
-
-
1
def matches?(message, *args)
-
@message == message && @argument_list_matcher.args_match?(*args)
-
end
-
-
1
def safe_invoke(parent_stub, *args, &block)
-
invoke_incrementing_actual_calls_by(1, false, parent_stub, *args, &block)
-
end
-
-
1
def invoke(parent_stub, *args, &block)
-
invoke_incrementing_actual_calls_by(1, true, parent_stub, *args, &block)
-
end
-
-
1
def invoke_without_incrementing_received_count(parent_stub, *args, &block)
-
invoke_incrementing_actual_calls_by(0, true, parent_stub, *args, &block)
-
end
-
-
1
def negative?
-
@expected_received_count == 0 && !@at_least
-
end
-
-
1
def called_max_times?
-
@expected_received_count != :any &&
-
!@at_least &&
-
@expected_received_count > 0 &&
-
@actual_received_count >= @expected_received_count
-
end
-
-
1
def matches_name_but_not_args(message, *args)
-
@message == message && !@argument_list_matcher.args_match?(*args)
-
end
-
-
1
def verify_messages_received
-
return if expected_messages_received?
-
generate_error
-
end
-
-
1
def expected_messages_received?
-
ignoring_args? || matches_exact_count? || matches_at_least_count? || matches_at_most_count?
-
end
-
-
1
def ensure_expected_ordering_received!
-
@order_group.verify_invocation_order(self) if @ordered
-
true
-
end
-
-
1
def ignoring_args?
-
@expected_received_count == :any
-
end
-
-
1
def matches_at_least_count?
-
@at_least && @actual_received_count >= @expected_received_count
-
end
-
-
1
def matches_at_most_count?
-
@at_most && @actual_received_count <= @expected_received_count
-
end
-
-
1
def matches_exact_count?
-
@expected_received_count == @actual_received_count
-
end
-
-
1
def similar_messages
-
@similar_messages ||= []
-
end
-
-
1
def advise(*args)
-
similar_messages << args
-
end
-
-
1
def unadvise(args)
-
similar_messages.delete_if { |message| args.include?(message) }
-
end
-
-
1
def generate_error
-
if similar_messages.empty?
-
@error_generator.raise_expectation_error(
-
@message, @expected_received_count, @argument_list_matcher,
-
@actual_received_count, expectation_count_type, expected_args,
-
@expected_from, exception_source_id
-
)
-
else
-
@error_generator.raise_similar_message_args_error(
-
self, @similar_messages, @expected_from
-
)
-
end
-
end
-
-
1
def raise_unexpected_message_args_error(args_for_multiple_calls)
-
@error_generator.raise_unexpected_message_args_error(self, args_for_multiple_calls, exception_source_id)
-
end
-
-
1
def expectation_count_type
-
return :at_least if @at_least
-
return :at_most if @at_most
-
nil
-
end
-
-
1
def description_for(verb)
-
@error_generator.describe_expectation(
-
verb, @message, @expected_received_count,
-
@actual_received_count, expected_args
-
)
-
end
-
-
1
def raise_out_of_order_error
-
@error_generator.raise_out_of_order_error @message
-
end
-
-
1
def additional_expected_calls
-
return 0 if @expectation_type == :stub || !@exactly
-
@expected_received_count - 1
-
end
-
-
1
def ordered?
-
@ordered
-
end
-
-
1
def negative_expectation_for?(message)
-
@message == message && negative?
-
end
-
-
1
def actual_received_count_matters?
-
@at_least || @at_most || @exactly
-
end
-
-
1
def increase_actual_received_count!
-
@actual_received_count += 1
-
end
-
-
1
private
-
-
1
def exception_source_id
-
@exception_source_id ||= "#{self.class.name} #{__id__}"
-
end
-
-
1
def invoke_incrementing_actual_calls_by(increment, allowed_to_fail, parent_stub, *args, &block)
-
args.unshift(orig_object) if yield_receiver_to_implementation_block?
-
-
if negative? || (allowed_to_fail && (@exactly || @at_most) && (@actual_received_count == @expected_received_count))
-
# args are the args we actually received, @argument_list_matcher is the
-
# list of args we were expecting
-
@error_generator.raise_expectation_error(
-
@message, @expected_received_count,
-
@argument_list_matcher,
-
@actual_received_count + increment,
-
expectation_count_type, args, nil, exception_source_id
-
)
-
end
-
-
@order_group.handle_order_constraint self
-
-
if implementation.present?
-
implementation.call(*args, &block)
-
elsif parent_stub
-
parent_stub.invoke(nil, *args, &block)
-
end
-
ensure
-
@actual_received_count += increment
-
end
-
-
1
def has_been_invoked?
-
@actual_received_count > 0
-
end
-
-
1
def raise_already_invoked_error_if_necessary(calling_customization)
-
return unless has_been_invoked?
-
-
error_generator.raise_already_invoked_error(message, calling_customization)
-
end
-
-
1
def set_expected_received_count(relativity, n)
-
@at_least = (relativity == :at_least)
-
@at_most = (relativity == :at_most)
-
@exactly = (relativity == :exactly)
-
@expected_received_count = case n
-
when Numeric then n
-
when :once then 1
-
when :twice then 2
-
when :thrice then 3
-
end
-
end
-
-
1
def initial_implementation_action=(action)
-
implementation.initial_action = action
-
end
-
-
1
def inner_implementation_action=(action)
-
return unless action
-
warn_about_stub_override if implementation.inner_action
-
implementation.inner_action = action
-
end
-
-
1
def terminal_implementation_action=(action)
-
implementation.terminal_action = action
-
end
-
-
1
def warn_about_stub_override
-
RSpec.warning(
-
"You're overriding a previous stub implementation of `#{@message}`. " \
-
"Called from #{CallerFilter.first_non_rspec_line}."
-
)
-
end
-
end
-
-
1
include ImplementationDetails
-
end
-
-
# Handles the implementation of an `and_yield` declaration.
-
# @private
-
1
class AndYieldImplementation
-
1
def initialize(args_to_yield, eval_context, error_generator)
-
@args_to_yield = args_to_yield
-
@eval_context = eval_context
-
@error_generator = error_generator
-
end
-
-
1
def call(*_args_to_ignore, &block)
-
return if @args_to_yield.empty? && @eval_context.nil?
-
-
@error_generator.raise_missing_block_error @args_to_yield unless block
-
value = nil
-
block_signature = Support::BlockSignature.new(block)
-
-
@args_to_yield.each do |args|
-
unless Support::StrictSignatureVerifier.new(block_signature, args).valid?
-
@error_generator.raise_wrong_arity_error(args, block_signature)
-
end
-
-
value = @eval_context ? @eval_context.instance_exec(*args, &block) : block.call(*args)
-
end
-
value
-
end
-
end
-
-
# Handles the implementation of an `and_return` implementation.
-
# @private
-
1
class AndReturnImplementation
-
1
def initialize(values_to_return)
-
@values_to_return = values_to_return
-
end
-
-
1
def call(*_args_to_ignore, &_block)
-
if @values_to_return.size > 1
-
@values_to_return.shift
-
else
-
@values_to_return.first
-
end
-
end
-
end
-
-
# Represents a configured implementation. Takes into account
-
# any number of sub-implementations.
-
# @private
-
1
class Implementation
-
1
attr_accessor :initial_action, :inner_action, :terminal_action
-
-
1
def call(*args, &block)
-
actions.map do |action|
-
action.call(*args, &block)
-
end.last
-
end
-
-
1
def present?
-
actions.any?
-
end
-
-
1
private
-
-
1
def actions
-
[initial_action, inner_action, terminal_action].compact
-
end
-
end
-
-
# Represents an `and_call_original` implementation.
-
# @private
-
1
class AndWrapOriginalImplementation
-
1
def initialize(method, block)
-
@method = method
-
@block = block
-
end
-
-
1
CannotModifyFurtherError = Class.new(StandardError)
-
-
1
def initial_action=(_value)
-
raise cannot_modify_further_error
-
end
-
-
1
def inner_action=(_value)
-
raise cannot_modify_further_error
-
end
-
-
1
def terminal_action=(_value)
-
raise cannot_modify_further_error
-
end
-
-
1
def present?
-
true
-
end
-
-
1
def inner_action
-
true
-
end
-
-
1
def call(*args, &block)
-
@block.call(@method, *args, &block)
-
end
-
-
1
private
-
-
1
def cannot_modify_further_error
-
CannotModifyFurtherError.new "This method has already been configured " \
-
"to call the original implementation, and cannot be modified further."
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class MethodDouble
-
# @private
-
1
attr_reader :method_name, :object, :expectations, :stubs
-
-
# @private
-
1
def initialize(object, method_name, proxy)
-
@method_name = method_name
-
@object = object
-
@proxy = proxy
-
-
@original_visibility = nil
-
@method_stasher = InstanceMethodStasher.new(object, method_name)
-
@method_is_proxied = false
-
@expectations = []
-
@stubs = []
-
end
-
-
1
def original_implementation_callable
-
# If original method is not present, uses the `method_missing`
-
# handler of the object. This accounts for cases where the user has not
-
# correctly defined `respond_to?`, and also 1.8 which does not provide
-
# method handles for missing methods even if `respond_to?` is correct.
-
@original_implementation_callable ||= original_method ||
-
Proc.new do |*args, &block|
-
@object.__send__(:method_missing, @method_name, *args, &block)
-
end
-
end
-
-
1
alias_method :save_original_implementation_callable!, :original_implementation_callable
-
-
1
def original_method
-
@original_method ||=
-
@method_stasher.original_method ||
-
@proxy.original_method_handle_for(method_name)
-
end
-
-
# @private
-
1
def visibility
-
@proxy.visibility_for(@method_name)
-
end
-
-
# @private
-
1
def object_singleton_class
-
class << @object; self; end
-
end
-
-
# @private
-
1
def configure_method
-
@original_visibility = visibility
-
@method_stasher.stash unless @method_is_proxied
-
define_proxy_method
-
end
-
-
# @private
-
1
def define_proxy_method
-
return if @method_is_proxied
-
-
save_original_implementation_callable!
-
definition_target.class_exec(self, method_name, visibility) do |method_double, method_name, visibility|
-
define_method(method_name) do |*args, &block|
-
method_double.proxy_method_invoked(self, *args, &block)
-
end
-
__send__(visibility, method_name)
-
end
-
-
@method_is_proxied = true
-
end
-
-
# The implementation of the proxied method. Subclasses may override this
-
# method to perform additional operations.
-
#
-
# @private
-
1
def proxy_method_invoked(_obj, *args, &block)
-
@proxy.message_received method_name, *args, &block
-
end
-
-
# @private
-
1
def restore_original_method
-
return show_frozen_warning if object_singleton_class.frozen?
-
return unless @method_is_proxied
-
-
remove_method_from_definition_target
-
@method_stasher.restore if @method_stasher.method_is_stashed?
-
restore_original_visibility
-
-
@method_is_proxied = false
-
end
-
-
# @private
-
1
def show_frozen_warning
-
RSpec.warn_with(
-
"WARNING: rspec-mocks was unable to restore the original `#{@method_name}` " \
-
"method on #{@object.inspect} because it has been frozen. If you reuse this " \
-
"object, `#{@method_name}` will continue to respond with its stub implementation.",
-
:call_site => nil,
-
:use_spec_location_as_call_site => true
-
)
-
end
-
-
# @private
-
1
def restore_original_visibility
-
return unless @original_visibility &&
-
MethodReference.method_defined_at_any_visibility?(object_singleton_class, @method_name)
-
-
object_singleton_class.__send__(@original_visibility, method_name)
-
end
-
-
# @private
-
1
def verify
-
expectations.each { |e| e.verify_messages_received }
-
end
-
-
# @private
-
1
def reset
-
restore_original_method
-
clear
-
end
-
-
# @private
-
1
def clear
-
expectations.clear
-
stubs.clear
-
end
-
-
# The type of message expectation to create has been extracted to its own
-
# method so that subclasses can override it.
-
#
-
# @private
-
1
def message_expectation_class
-
MessageExpectation
-
end
-
-
# @private
-
1
def add_expectation(error_generator, expectation_ordering, expected_from, opts, &implementation)
-
configure_method
-
expectation = message_expectation_class.new(error_generator, expectation_ordering,
-
expected_from, self, :expectation, opts, &implementation)
-
expectations << expectation
-
expectation
-
end
-
-
# @private
-
1
def build_expectation(error_generator, expectation_ordering)
-
expected_from = IGNORED_BACKTRACE_LINE
-
message_expectation_class.new(error_generator, expectation_ordering, expected_from, self)
-
end
-
-
# @private
-
1
def add_stub(error_generator, expectation_ordering, expected_from, opts={}, &implementation)
-
configure_method
-
stub = message_expectation_class.new(error_generator, expectation_ordering, expected_from,
-
self, :stub, opts, &implementation)
-
stubs.unshift stub
-
stub
-
end
-
-
# A simple stub can only return a concrete value for a message, and
-
# cannot match on arguments. It is used as an optimization over
-
# `add_stub` / `add_expectation` where it is known in advance that this
-
# is all that will be required of a stub, such as when passing attributes
-
# to the `double` example method. They do not stash or restore existing method
-
# definitions.
-
#
-
# @private
-
1
def add_simple_stub(method_name, response)
-
setup_simple_method_double method_name, response, stubs
-
end
-
-
# @private
-
1
def add_simple_expectation(method_name, response, error_generator, backtrace_line)
-
setup_simple_method_double method_name, response, expectations, error_generator, backtrace_line
-
end
-
-
# @private
-
1
def setup_simple_method_double(method_name, response, collection, error_generator=nil, backtrace_line=nil)
-
define_proxy_method
-
-
me = SimpleMessageExpectation.new(method_name, response, error_generator, backtrace_line)
-
collection.unshift me
-
me
-
end
-
-
# @private
-
1
def add_default_stub(*args, &implementation)
-
return if stubs.any?
-
add_stub(*args, &implementation)
-
end
-
-
# @private
-
1
def remove_stub
-
raise_method_not_stubbed_error if stubs.empty?
-
remove_stub_if_present
-
end
-
-
# @private
-
1
def remove_stub_if_present
-
expectations.empty? ? reset : stubs.clear
-
end
-
-
# @private
-
1
def raise_method_not_stubbed_error
-
RSpec::Mocks.error_generator.raise_method_not_stubbed_error(method_name)
-
end
-
-
# In Ruby 2.0.0 and above prepend will alter the method lookup chain.
-
# We use an object's singleton class to define method doubles upon,
-
# however if the object has had it's singleton class (as opposed to
-
# it's actual class) prepended too then the the method lookup chain
-
# will look in the prepended module first, **before** the singleton
-
# class.
-
#
-
# This code works around that by providing a mock definition target
-
# that is either the singleton class, or if necessary, a prepended module
-
# of our own.
-
#
-
1
if Support::RubyFeatures.module_prepends_supported?
-
-
1
private
-
-
# We subclass `Module` in order to be able to easily detect our prepended module.
-
1
RSpecPrependedModule = Class.new(Module)
-
-
1
def definition_target
-
@definition_target ||= usable_rspec_prepended_module || object_singleton_class
-
end
-
-
1
def usable_rspec_prepended_module
-
@proxy.prepended_modules_of_singleton_class.each do |mod|
-
# If we have one of our modules prepended before one of the user's
-
# modules that defines the method, use that, since our module's
-
# definition will take precedence.
-
return mod if RSpecPrependedModule === mod
-
-
# If we hit a user module with the method defined first,
-
# we must create a new prepend module, even if one exists later,
-
# because ours will only take precedence if it comes first.
-
return new_rspec_prepended_module if mod.method_defined?(method_name)
-
end
-
-
nil
-
end
-
-
1
def new_rspec_prepended_module
-
RSpecPrependedModule.new.tap do |mod|
-
object_singleton_class.__send__ :prepend, mod
-
end
-
end
-
-
else
-
-
private
-
-
def definition_target
-
object_singleton_class
-
end
-
-
end
-
-
1
private
-
-
1
def remove_method_from_definition_target
-
definition_target.__send__(:remove_method, @method_name)
-
rescue NameError
-
# This can happen when the method has been monkeyed with by
-
# something outside RSpec. This happens, for example, when
-
# `file.write` has been stubbed, and then `file.reopen(other_io)`
-
# is later called, as `File#reopen` appears to redefine `write`.
-
#
-
# Note: we could avoid rescuing this by checking
-
# `definition_target.instance_method(@method_name).owner == definition_target`,
-
# saving us from the cost of the expensive exception, but this error is
-
# extremely rare (it was discovered on 2014-12-30, only happens on
-
# RUBY_VERSION < 2.0 and our spec suite only hits this condition once),
-
# so we'd rather avoid the cost of that check for every method double,
-
# and risk the rare situation where this exception will get raised.
-
RSpec.warn_with(
-
"WARNING: RSpec could not fully restore #{@object.inspect}." \
-
"#{@method_name}, possibly because the method has been redefined " \
-
"by something outside of RSpec."
-
)
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Represents a method on an object that may or may not be defined.
-
# The method may be an instance method on a module or a method on
-
# any object.
-
#
-
# @private
-
1
class MethodReference
-
1
def self.for(object_reference, method_name)
-
new(object_reference, method_name)
-
end
-
-
1
def initialize(object_reference, method_name)
-
@object_reference = object_reference
-
@method_name = method_name
-
end
-
-
# A method is implemented if sending the message does not result in
-
# a `NoMethodError`. It might be dynamically implemented by
-
# `method_missing`.
-
1
def implemented?
-
@object_reference.when_loaded do |m|
-
method_implemented?(m)
-
end
-
end
-
-
# Returns true if we definitively know that sending the method
-
# will result in a `NoMethodError`.
-
#
-
# This is not simply the inverse of `implemented?`: there are
-
# cases when we don't know if a method is implemented and
-
# both `implemented?` and `unimplemented?` will return false.
-
1
def unimplemented?
-
@object_reference.when_loaded do |_m|
-
return !implemented?
-
end
-
-
# If it's not loaded, then it may be implemented but we can't check.
-
false
-
end
-
-
# A method is defined if we are able to get a `Method` object for it.
-
# In that case, we can assert against metadata like the arity.
-
1
def defined?
-
@object_reference.when_loaded do |m|
-
method_defined?(m)
-
end
-
end
-
-
1
def with_signature
-
return unless (original = original_method)
-
yield Support::MethodSignature.new(original)
-
end
-
-
1
def visibility
-
@object_reference.when_loaded do |m|
-
return visibility_from(m)
-
end
-
-
# When it's not loaded, assume it's public. We don't want to
-
# wrongly treat the method as private.
-
:public
-
end
-
-
1
private
-
-
1
def original_method
-
@object_reference.when_loaded do |m|
-
self.defined? && find_method(m)
-
end
-
end
-
-
1
def self.instance_method_visibility_for(klass, method_name)
-
if klass.public_method_defined?(method_name)
-
:public
-
elsif klass.private_method_defined?(method_name)
-
:private
-
elsif klass.protected_method_defined?(method_name)
-
:protected
-
end
-
end
-
-
1
class << self
-
1
alias method_defined_at_any_visibility? instance_method_visibility_for
-
end
-
-
1
def self.method_visibility_for(object, method_name)
-
instance_method_visibility_for(class << object; self; end, method_name).tap do |vis|
-
# If the method is not defined on the class, `instance_method_visibility_for`
-
# returns `nil`. However, it may be handled dynamically by `method_missing`,
-
# so here we check `respond_to` (passing false to not check private methods).
-
#
-
# This only considers the public case, but I don't think it's possible to
-
# write `method_missing` in such a way that it handles a dynamic message
-
# with private or protected visibility. Ruby doesn't provide you with
-
# the caller info.
-
return :public if vis.nil? && object.respond_to?(method_name, false)
-
end
-
end
-
end
-
-
# @private
-
1
class InstanceMethodReference < MethodReference
-
1
private
-
-
1
def method_implemented?(mod)
-
MethodReference.method_defined_at_any_visibility?(mod, @method_name)
-
end
-
-
# Ideally, we'd use `respond_to?` for `method_implemented?` but we need a
-
# reference to an instance to do that and we don't have one. Note that
-
# we may get false negatives: if the method is implemented via
-
# `method_missing`, we'll return `false` even though it meets our
-
# definition of "implemented". However, it's the best we can do.
-
1
alias method_defined? method_implemented?
-
-
# works around the fact that repeated calls for method parameters will
-
# falsely return empty arrays on JRuby in certain circumstances, this
-
# is necessary here because we can't dup/clone UnboundMethods.
-
#
-
# This is necessary due to a bug in JRuby prior to 1.7.5 fixed in:
-
# https://github.com/jruby/jruby/commit/99a0613fe29935150d76a9a1ee4cf2b4f63f4a27
-
1
if RUBY_PLATFORM == 'java' && JRUBY_VERSION.split('.')[-1].to_i < 5
-
def find_method(mod)
-
mod.dup.instance_method(@method_name)
-
end
-
else
-
1
def find_method(mod)
-
mod.instance_method(@method_name)
-
end
-
end
-
-
1
def visibility_from(mod)
-
MethodReference.instance_method_visibility_for(mod, @method_name)
-
end
-
end
-
-
# @private
-
1
class ObjectMethodReference < MethodReference
-
1
def self.for(object_reference, method_name)
-
if ClassNewMethodReference.applies_to?(method_name) { object_reference.when_loaded { |o| o } }
-
ClassNewMethodReference.new(object_reference, method_name)
-
else
-
super
-
end
-
end
-
-
1
private
-
-
1
def method_implemented?(object)
-
object.respond_to?(@method_name, true)
-
end
-
-
1
def method_defined?(object)
-
(class << object; self; end).method_defined?(@method_name)
-
end
-
-
1
def find_method(object)
-
object.method(@method_name)
-
end
-
-
1
def visibility_from(object)
-
MethodReference.method_visibility_for(object, @method_name)
-
end
-
end
-
-
# When a class's `.new` method is stubbed, we want to use the method
-
# signature from `#initialize` because `.new`'s signature is a generic
-
# `def new(*args)` and it simply delegates to `#initialize` and forwards
-
# all args...so the method with the actually used signature is `#initialize`.
-
#
-
# This method reference implementation handles that specific case.
-
# @private
-
1
class ClassNewMethodReference < ObjectMethodReference
-
1
def self.applies_to?(method_name)
-
return false unless method_name == :new
-
klass = yield
-
return false unless klass.respond_to?(:new, true)
-
-
# We only want to apply our special logic to normal `new` methods.
-
# Methods that the user has monkeyed with should be left as-is.
-
klass.method(:new).owner == ::Class
-
end
-
-
1
def with_signature
-
@object_reference.when_loaded do |klass|
-
yield Support::MethodSignature.new(klass.instance_method(:initialize))
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'recursive_const_methods'
-
-
1
module RSpec
-
1
module Mocks
-
# Provides information about constants that may (or may not)
-
# have been mutated by rspec-mocks.
-
1
class Constant
-
1
extend Support::RecursiveConstMethods
-
-
# @api private
-
1
def initialize(name)
-
@name = name
-
@previously_defined = false
-
@stubbed = false
-
@hidden = false
-
@valid_name = true
-
yield self if block_given?
-
end
-
-
# @return [String] The fully qualified name of the constant.
-
1
attr_reader :name
-
-
# @return [Object, nil] The original value (e.g. before it
-
# was mutated by rspec-mocks) of the constant, or
-
# nil if the constant was not previously defined.
-
1
attr_accessor :original_value
-
-
# @private
-
1
attr_writer :previously_defined, :stubbed, :hidden, :valid_name
-
-
# @return [Boolean] Whether or not the constant was defined
-
# before the current example.
-
1
def previously_defined?
-
@previously_defined
-
end
-
-
# @return [Boolean] Whether or not rspec-mocks has mutated
-
# (stubbed or hidden) this constant.
-
1
def mutated?
-
@stubbed || @hidden
-
end
-
-
# @return [Boolean] Whether or not rspec-mocks has stubbed
-
# this constant.
-
1
def stubbed?
-
@stubbed
-
end
-
-
# @return [Boolean] Whether or not rspec-mocks has hidden
-
# this constant.
-
1
def hidden?
-
@hidden
-
end
-
-
# @return [Boolean] Whether or not the provided constant name
-
# is a valid Ruby constant name.
-
1
def valid_name?
-
@valid_name
-
end
-
-
# The default `to_s` isn't very useful, so a custom version is provided.
-
1
def to_s
-
"#<#{self.class.name} #{name}>"
-
end
-
1
alias inspect to_s
-
-
# @private
-
1
def self.unmutated(name)
-
previously_defined = recursive_const_defined?(name)
-
rescue NameError
-
new(name) do |c|
-
c.valid_name = false
-
end
-
else
-
new(name) do |const|
-
const.previously_defined = previously_defined
-
const.original_value = recursive_const_get(name) if previously_defined
-
end
-
end
-
-
# Queries rspec-mocks to find out information about the named constant.
-
#
-
# @param [String] name the name of the constant
-
# @return [Constant] an object contaning information about the named
-
# constant.
-
1
def self.original(name)
-
mutator = ::RSpec::Mocks.space.constant_mutator_for(name)
-
mutator ? mutator.to_constant : unmutated(name)
-
end
-
end
-
-
# Provides a means to stub constants.
-
1
class ConstantMutator
-
1
extend Support::RecursiveConstMethods
-
-
# Stubs a constant.
-
#
-
# @param (see ExampleMethods#stub_const)
-
# @option (see ExampleMethods#stub_const)
-
# @return (see ExampleMethods#stub_const)
-
#
-
# @see ExampleMethods#stub_const
-
# @note It's recommended that you use `stub_const` in your
-
# examples. This is an alternate public API that is provided
-
# so you can stub constants in other contexts (e.g. helper
-
# classes).
-
1
def self.stub(constant_name, value, options={})
-
mutator = if recursive_const_defined?(constant_name, &raise_on_invalid_const)
-
DefinedConstantReplacer
-
else
-
UndefinedConstantSetter
-
end
-
-
mutate(mutator.new(constant_name, value, options[:transfer_nested_constants]))
-
value
-
end
-
-
# Hides a constant.
-
#
-
# @param (see ExampleMethods#hide_const)
-
#
-
# @see ExampleMethods#hide_const
-
# @note It's recommended that you use `hide_const` in your
-
# examples. This is an alternate public API that is provided
-
# so you can hide constants in other contexts (e.g. helper
-
# classes).
-
1
def self.hide(constant_name)
-
mutate(ConstantHider.new(constant_name, nil, {}))
-
nil
-
end
-
-
# Contains common functionality used by all of the constant mutators.
-
#
-
# @private
-
1
class BaseMutator
-
1
include Support::RecursiveConstMethods
-
-
1
attr_reader :original_value, :full_constant_name
-
-
1
def initialize(full_constant_name, mutated_value, transfer_nested_constants)
-
@full_constant_name = normalize_const_name(full_constant_name)
-
@mutated_value = mutated_value
-
@transfer_nested_constants = transfer_nested_constants
-
@context_parts = @full_constant_name.split('::')
-
@const_name = @context_parts.pop
-
@reset_performed = false
-
end
-
-
1
def to_constant
-
const = Constant.new(full_constant_name)
-
const.original_value = original_value
-
-
const
-
end
-
-
1
def idempotently_reset
-
reset unless @reset_performed
-
@reset_performed = true
-
end
-
end
-
-
# Hides a defined constant for the duration of an example.
-
#
-
# @private
-
1
class ConstantHider < BaseMutator
-
1
def mutate
-
return unless (@defined = recursive_const_defined?(full_constant_name))
-
@context = recursive_const_get(@context_parts.join('::'))
-
@original_value = get_const_defined_on(@context, @const_name)
-
-
@context.__send__(:remove_const, @const_name)
-
end
-
-
1
def to_constant
-
return Constant.unmutated(full_constant_name) unless @defined
-
-
const = super
-
const.hidden = true
-
const.previously_defined = true
-
-
const
-
end
-
-
1
def reset
-
return unless @defined
-
@context.const_set(@const_name, @original_value)
-
end
-
end
-
-
# Replaces a defined constant for the duration of an example.
-
#
-
# @private
-
1
class DefinedConstantReplacer < BaseMutator
-
1
def initialize(*args)
-
super
-
@constants_to_transfer = []
-
end
-
-
1
def mutate
-
@context = recursive_const_get(@context_parts.join('::'))
-
@original_value = get_const_defined_on(@context, @const_name)
-
-
@constants_to_transfer = verify_constants_to_transfer!
-
-
@context.__send__(:remove_const, @const_name)
-
@context.const_set(@const_name, @mutated_value)
-
-
transfer_nested_constants
-
end
-
-
1
def to_constant
-
const = super
-
const.stubbed = true
-
const.previously_defined = true
-
-
const
-
end
-
-
1
def reset
-
@constants_to_transfer.each do |const|
-
@mutated_value.__send__(:remove_const, const)
-
end
-
-
@context.__send__(:remove_const, @const_name)
-
@context.const_set(@const_name, @original_value)
-
end
-
-
1
def transfer_nested_constants
-
@constants_to_transfer.each do |const|
-
@mutated_value.const_set(const, get_const_defined_on(original_value, const))
-
end
-
end
-
-
1
def verify_constants_to_transfer!
-
return [] unless should_transfer_nested_constants?
-
-
{ @original_value => "the original value", @mutated_value => "the stubbed value" }.each do |value, description|
-
next if value.respond_to?(:constants)
-
-
raise ArgumentError,
-
"Cannot transfer nested constants for #{@full_constant_name} " \
-
"since #{description} is not a class or module and only classes " \
-
"and modules support nested constants."
-
end
-
-
if Array === @transfer_nested_constants
-
@transfer_nested_constants = @transfer_nested_constants.map(&:to_s) if RUBY_VERSION == '1.8.7'
-
undefined_constants = @transfer_nested_constants - constants_defined_on(@original_value)
-
-
if undefined_constants.any?
-
available_constants = constants_defined_on(@original_value) - @transfer_nested_constants
-
raise ArgumentError,
-
"Cannot transfer nested constant(s) #{undefined_constants.join(' and ')} " \
-
"for #{@full_constant_name} since they are not defined. Did you mean " \
-
"#{available_constants.join(' or ')}?"
-
end
-
-
@transfer_nested_constants
-
else
-
constants_defined_on(@original_value)
-
end
-
end
-
-
1
def should_transfer_nested_constants?
-
return true if @transfer_nested_constants
-
return false unless RSpec::Mocks.configuration.transfer_nested_constants?
-
@original_value.respond_to?(:constants) && @mutated_value.respond_to?(:constants)
-
end
-
end
-
-
# Sets an undefined constant for the duration of an example.
-
#
-
# @private
-
1
class UndefinedConstantSetter < BaseMutator
-
1
def mutate
-
@parent = @context_parts.inject(Object) do |klass, name|
-
if const_defined_on?(klass, name)
-
get_const_defined_on(klass, name)
-
else
-
ConstantMutator.stub(name_for(klass, name), Module.new)
-
end
-
end
-
-
@parent.const_set(@const_name, @mutated_value)
-
end
-
-
1
def to_constant
-
const = super
-
const.stubbed = true
-
const.previously_defined = false
-
-
const
-
end
-
-
1
def reset
-
@parent.__send__(:remove_const, @const_name)
-
end
-
-
1
private
-
-
1
def name_for(parent, name)
-
root = if parent == Object
-
''
-
else
-
parent.name
-
end
-
root + '::' + name
-
end
-
end
-
-
# Uses the mutator to mutate (stub or hide) a constant. Ensures that
-
# the mutator is correctly registered so it can be backed out at the end
-
# of the test.
-
#
-
# @private
-
1
def self.mutate(mutator)
-
::RSpec::Mocks.space.register_constant_mutator(mutator)
-
mutator.mutate
-
end
-
-
# Used internally by the constant stubbing to raise a helpful
-
# error when a constant like "A::B::C" is stubbed and A::B is
-
# not a module (and thus, it's impossible to define "A::B::C"
-
# since only modules can have nested constants).
-
#
-
# @api private
-
1
def self.raise_on_invalid_const
-
lambda do |const_name, failed_name|
-
raise "Cannot stub constant #{failed_name} on #{const_name} " \
-
"since #{const_name} is not a module."
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class ObjectReference
-
# Returns an appropriate Object or Module reference based
-
# on the given argument.
-
1
def self.for(object_module_or_name, allow_direct_object_refs=false)
-
case object_module_or_name
-
when Module
-
if anonymous_module?(object_module_or_name)
-
DirectObjectReference.new(object_module_or_name)
-
else
-
# Use a `NamedObjectReference` if it has a name because this
-
# will use the original value of the constant in case it has
-
# been stubbed.
-
NamedObjectReference.new(name_of(object_module_or_name))
-
end
-
when String
-
NamedObjectReference.new(object_module_or_name)
-
else
-
if allow_direct_object_refs
-
DirectObjectReference.new(object_module_or_name)
-
else
-
raise ArgumentError,
-
"Module or String expected, got #{object_module_or_name.inspect}"
-
end
-
end
-
end
-
-
1
if Module.new.name.nil?
-
1
def self.anonymous_module?(mod)
-
!name_of(mod)
-
end
-
else # 1.8.7
-
def self.anonymous_module?(mod)
-
name_of(mod) == ""
-
end
-
end
-
1
private_class_method :anonymous_module?
-
-
1
def self.name_of(mod)
-
MODULE_NAME_METHOD.bind(mod).call
-
end
-
1
private_class_method :name_of
-
-
# @private
-
1
MODULE_NAME_METHOD = Module.instance_method(:name)
-
end
-
-
# An implementation of rspec-mocks' reference interface.
-
# Used when an object is passed to {ExampleMethods#object_double}, or
-
# an anonymous class or module is passed to {ExampleMethods#instance_double}
-
# or {ExampleMethods#class_double}.
-
# Represents a reference to that object.
-
# @see NamedObjectReference
-
1
class DirectObjectReference
-
# @param object [Object] the object to which this refers
-
1
def initialize(object)
-
@object = object
-
end
-
-
# @return [String] the object's description (via `#inspect`).
-
1
def description
-
@object.inspect
-
end
-
-
# Defined for interface parity with the other object reference
-
# implementations. Raises an `ArgumentError` to indicate that `as_stubbed_const`
-
# is invalid when passing an object argument to `object_double`.
-
1
def const_to_replace
-
raise ArgumentError,
-
"Can not perform constant replacement with an anonymous object."
-
end
-
-
# The target of the verifying double (the object itself).
-
#
-
# @return [Object]
-
1
def target
-
@object
-
end
-
-
# Always returns true for an object as the class is defined.
-
#
-
# @return [true]
-
1
def defined?
-
true
-
end
-
-
# Yields if the reference target is loaded, providing a generic mechanism
-
# to optionally run a bit of code only when a reference's target is
-
# loaded.
-
#
-
# This specific implementation always yields because direct references
-
# are always loaded.
-
#
-
# @yield [Object] the target of this reference.
-
1
def when_loaded
-
yield @object
-
end
-
end
-
-
# An implementation of rspec-mocks' reference interface.
-
# Used when a string is passed to {ExampleMethods#object_double},
-
# and when a string, named class or named module is passed to
-
# {ExampleMethods#instance_double}, or {ExampleMethods#class_double}.
-
# Represents a reference to the object named (via a constant lookup)
-
# by the string.
-
# @see DirectObjectReference
-
1
class NamedObjectReference
-
# @param const_name [String] constant name
-
1
def initialize(const_name)
-
@const_name = const_name
-
end
-
-
# @return [Boolean] true if the named constant is defined, false otherwise.
-
1
def defined?
-
!!object
-
end
-
-
# @return [String] the constant name to replace with a double.
-
1
def const_to_replace
-
@const_name
-
end
-
1
alias description const_to_replace
-
-
# @return [Object, nil] the target of the verifying double (the named object), or
-
# nil if it is not defined.
-
1
def target
-
object
-
end
-
-
# Yields if the reference target is loaded, providing a generic mechanism
-
# to optionally run a bit of code only when a reference's target is
-
# loaded.
-
#
-
# @yield [Object] the target object
-
1
def when_loaded
-
yield object if object
-
end
-
-
1
private
-
-
1
def object
-
return @object if defined?(@object)
-
@object = Constant.original(@const_name).original_value
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class OrderGroup
-
1
def initialize
-
7
@expectations = []
-
7
@invocation_order = []
-
7
@index = 0
-
end
-
-
# @private
-
1
def register(expectation)
-
@expectations << expectation
-
end
-
-
1
def invoked(message)
-
@invocation_order << message
-
end
-
-
# @private
-
1
def ready_for?(expectation)
-
remaining_expectations.find(&:ordered?) == expectation
-
end
-
-
# @private
-
1
def consume
-
remaining_expectations.each_with_index do |expectation, index|
-
next unless expectation.ordered?
-
-
@index += index + 1
-
return expectation
-
end
-
nil
-
end
-
-
# @private
-
1
def handle_order_constraint(expectation)
-
return unless expectation.ordered? && remaining_expectations.include?(expectation)
-
return consume if ready_for?(expectation)
-
expectation.raise_out_of_order_error
-
end
-
-
1
def verify_invocation_order(expectation)
-
expectation.raise_out_of_order_error unless expectations_invoked_in_order?
-
true
-
end
-
-
1
def clear
-
@index = 0
-
@invocation_order.clear
-
@expectations.clear
-
end
-
-
1
def empty?
-
@expectations.empty?
-
end
-
-
1
private
-
-
1
def remaining_expectations
-
@expectations[@index..-1] || []
-
end
-
-
1
def expectations_invoked_in_order?
-
invoked_expectations == expected_invocations
-
end
-
-
1
def invoked_expectations
-
@expectations.select { |e| e.ordered? && @invocation_order.include?(e) }
-
end
-
-
1
def expected_invocations
-
@invocation_order.map { |invocation| expectation_for(invocation) }.compact
-
end
-
-
1
def expectation_for(message)
-
@expectations.find { |e| message == e }
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class Proxy
-
1
SpecificMessage = Struct.new(:object, :message, :args) do
-
1
def ==(expectation)
-
expectation.orig_object == object && expectation.matches?(message, *args)
-
end
-
end
-
-
# @private
-
1
def ensure_implemented(*_args)
-
# noop for basic proxies, see VerifyingProxy for behaviour.
-
end
-
-
# @private
-
1
def initialize(object, order_group, options={})
-
@object = object
-
@order_group = order_group
-
@error_generator = ErrorGenerator.new(object)
-
@messages_received = []
-
@options = options
-
@null_object = false
-
@method_doubles = Hash.new { |h, k| h[k] = MethodDouble.new(@object, k, self) }
-
end
-
-
# @private
-
1
attr_reader :object
-
-
# @private
-
1
def null_object?
-
@null_object
-
end
-
-
# @private
-
# Tells the object to ignore any messages that aren't explicitly set as
-
# stubs or message expectations.
-
1
def as_null_object
-
@null_object = true
-
@object
-
end
-
-
# @private
-
1
def original_method_handle_for(_message)
-
nil
-
end
-
-
1
DEFAULT_MESSAGE_EXPECTATION_OPTS = {}.freeze
-
-
# @private
-
1
def add_message_expectation(method_name, opts=DEFAULT_MESSAGE_EXPECTATION_OPTS, &block)
-
location = opts.fetch(:expected_from) { CallerFilter.first_non_rspec_line }
-
meth_double = method_double_for(method_name)
-
-
if null_object? && !block
-
meth_double.add_default_stub(@error_generator, @order_group, location, opts) do
-
@object
-
end
-
end
-
-
meth_double.add_expectation @error_generator, @order_group, location, opts, &block
-
end
-
-
# @private
-
1
def add_simple_expectation(method_name, response, location)
-
method_double_for(method_name).add_simple_expectation method_name, response, @error_generator, location
-
end
-
-
# @private
-
1
def build_expectation(method_name)
-
meth_double = method_double_for(method_name)
-
-
meth_double.build_expectation(
-
@error_generator,
-
@order_group
-
)
-
end
-
-
# @private
-
1
def replay_received_message_on(expectation, &block)
-
expected_method_name = expectation.message
-
meth_double = method_double_for(expected_method_name)
-
-
if meth_double.expectations.any?
-
@error_generator.raise_expectation_on_mocked_method(expected_method_name)
-
end
-
-
unless null_object? || meth_double.stubs.any?
-
@error_generator.raise_expectation_on_unstubbed_method(expected_method_name)
-
end
-
-
@messages_received.each do |(actual_method_name, args, _)|
-
next unless expectation.matches?(actual_method_name, *args)
-
-
expectation.safe_invoke(nil)
-
block.call(*args) if block
-
end
-
end
-
-
# @private
-
1
def check_for_unexpected_arguments(expectation)
-
return if @messages_received.empty?
-
-
return if @messages_received.any? { |method_name, args, _| expectation.matches?(method_name, *args) }
-
-
name_but_not_args, others = @messages_received.partition do |(method_name, args, _)|
-
expectation.matches_name_but_not_args(method_name, *args)
-
end
-
-
return if name_but_not_args.empty? && !others.empty?
-
-
expectation.raise_unexpected_message_args_error(name_but_not_args.map { |args| args[1] })
-
end
-
-
# @private
-
1
def add_stub(method_name, opts={}, &implementation)
-
location = opts.fetch(:expected_from) { CallerFilter.first_non_rspec_line }
-
method_double_for(method_name).add_stub @error_generator, @order_group, location, opts, &implementation
-
end
-
-
# @private
-
1
def add_simple_stub(method_name, response)
-
method_double_for(method_name).add_simple_stub method_name, response
-
end
-
-
# @private
-
1
def remove_stub(method_name)
-
method_double_for(method_name).remove_stub
-
end
-
-
# @private
-
1
def remove_stub_if_present(method_name)
-
method_double_for(method_name).remove_stub_if_present
-
end
-
-
# @private
-
1
def verify
-
@method_doubles.each_value { |d| d.verify }
-
end
-
-
# @private
-
1
def reset
-
@messages_received.clear
-
end
-
-
# @private
-
1
def received_message?(method_name, *args, &block)
-
@messages_received.any? { |array| array == [method_name, args, block] }
-
end
-
-
# @private
-
1
def messages_arg_list
-
@messages_received.map { |_, args, _| args }
-
end
-
-
# @private
-
1
def has_negative_expectation?(message)
-
method_double_for(message).expectations.find { |expectation| expectation.negative_expectation_for?(message) }
-
end
-
-
# @private
-
1
def record_message_received(message, *args, &block)
-
@order_group.invoked SpecificMessage.new(object, message, args)
-
@messages_received << [message, args, block]
-
end
-
-
# @private
-
1
def message_received(message, *args, &block)
-
record_message_received message, *args, &block
-
-
expectation = find_matching_expectation(message, *args)
-
stub = find_matching_method_stub(message, *args)
-
-
if (stub && expectation && expectation.called_max_times?) || (stub && !expectation)
-
expectation.increase_actual_received_count! if expectation && expectation.actual_received_count_matters?
-
if (expectation = find_almost_matching_expectation(message, *args))
-
expectation.advise(*args) unless expectation.expected_messages_received?
-
end
-
stub.invoke(nil, *args, &block)
-
elsif expectation
-
expectation.unadvise(messages_arg_list)
-
expectation.invoke(stub, *args, &block)
-
elsif (expectation = find_almost_matching_expectation(message, *args))
-
expectation.advise(*args) if null_object? unless expectation.expected_messages_received?
-
-
if null_object? || !has_negative_expectation?(message)
-
expectation.raise_unexpected_message_args_error([args])
-
end
-
elsif (stub = find_almost_matching_stub(message, *args))
-
stub.advise(*args)
-
raise_missing_default_stub_error(stub, [args])
-
elsif Class === @object
-
@object.superclass.__send__(message, *args, &block)
-
else
-
@object.__send__(:method_missing, message, *args, &block)
-
end
-
end
-
-
# @private
-
1
def raise_unexpected_message_error(method_name, args)
-
@error_generator.raise_unexpected_message_error method_name, args
-
end
-
-
# @private
-
1
def raise_missing_default_stub_error(expectation, args_for_multiple_calls)
-
@error_generator.raise_missing_default_stub_error(expectation, args_for_multiple_calls)
-
end
-
-
# @private
-
1
def visibility_for(_method_name)
-
# This is the default (for test doubles). Subclasses override this.
-
:public
-
end
-
-
1
if Support::RubyFeatures.module_prepends_supported?
-
1
def self.prepended_modules_of(klass)
-
ancestors = klass.ancestors
-
-
# `|| 0` is necessary for Ruby 2.0, where the singleton class
-
# is only in the ancestor list when there are prepended modules.
-
singleton_index = ancestors.index(klass) || 0
-
-
ancestors[0, singleton_index]
-
end
-
-
1
def prepended_modules_of_singleton_class
-
@prepended_modules_of_singleton_class ||= RSpec::Mocks::Proxy.prepended_modules_of(@object.singleton_class)
-
end
-
end
-
-
1
private
-
-
1
def method_double_for(message)
-
@method_doubles[message.to_sym]
-
end
-
-
1
def find_matching_expectation(method_name, *args)
-
find_best_matching_expectation_for(method_name) do |expectation|
-
expectation.matches?(method_name, *args)
-
end
-
end
-
-
1
def find_almost_matching_expectation(method_name, *args)
-
find_best_matching_expectation_for(method_name) do |expectation|
-
expectation.matches_name_but_not_args(method_name, *args)
-
end
-
end
-
-
1
def find_best_matching_expectation_for(method_name)
-
first_match = nil
-
-
method_double_for(method_name).expectations.each do |expectation|
-
next unless yield expectation
-
return expectation unless expectation.called_max_times?
-
first_match ||= expectation
-
end
-
-
first_match
-
end
-
-
1
def find_matching_method_stub(method_name, *args)
-
method_double_for(method_name).stubs.find { |stub| stub.matches?(method_name, *args) }
-
end
-
-
1
def find_almost_matching_stub(method_name, *args)
-
method_double_for(method_name).stubs.find { |stub| stub.matches_name_but_not_args(method_name, *args) }
-
end
-
end
-
-
# @private
-
1
class TestDoubleProxy < Proxy
-
1
def reset
-
@method_doubles.clear
-
object.__disallow_further_usage!
-
super
-
end
-
end
-
-
# @private
-
1
class PartialDoubleProxy < Proxy
-
1
def original_method_handle_for(message)
-
if any_instance_class_recorder_observing_method?(@object.class, message)
-
message = ::RSpec::Mocks.space.
-
any_instance_recorder_for(@object.class).
-
build_alias_method_name(message)
-
end
-
-
::RSpec::Support.method_handle_for(@object, message)
-
rescue NameError
-
nil
-
end
-
-
# @private
-
1
def add_simple_expectation(method_name, response, location)
-
method_double_for(method_name).configure_method
-
super
-
end
-
-
# @private
-
1
def add_simple_stub(method_name, response)
-
method_double_for(method_name).configure_method
-
super
-
end
-
-
# @private
-
1
def visibility_for(method_name)
-
# We fall back to :public because by default we allow undefined methods
-
# to be stubbed, and when we do so, we make them public.
-
MethodReference.method_visibility_for(@object, method_name) || :public
-
end
-
-
1
def reset
-
@method_doubles.each_value { |d| d.reset }
-
super
-
end
-
-
1
def message_received(message, *args, &block)
-
RSpec::Mocks.space.any_instance_recorders_from_ancestry_of(object).each do |subscriber|
-
subscriber.notify_received_message(object, message, args, block)
-
end
-
super
-
end
-
-
1
private
-
-
1
def any_instance_class_recorder_observing_method?(klass, method_name)
-
only_return_existing = true
-
recorder = ::RSpec::Mocks.space.any_instance_recorder_for(klass, only_return_existing)
-
return true if recorder && recorder.already_observing?(method_name)
-
-
superklass = klass.superclass
-
return false if superklass.nil?
-
any_instance_class_recorder_observing_method?(superklass, method_name)
-
end
-
end
-
-
# @private
-
# When we mock or stub a method on a class, we have to treat it a bit different,
-
# because normally singleton method definitions only affect the object on which
-
# they are defined, but on classes they affect subclasses, too. As a result,
-
# we need some special handling to get the original method.
-
1
module PartialClassDoubleProxyMethods
-
1
def initialize(source_space, *args)
-
@source_space = source_space
-
super(*args)
-
end
-
-
# Consider this situation:
-
#
-
# class A; end
-
# class B < A; end
-
#
-
# allow(A).to receive(:new)
-
# expect(B).to receive(:new).and_call_original
-
#
-
# When getting the original definition for `B.new`, we cannot rely purely on
-
# using `B.method(:new)` before our redefinition is defined on `B`, because
-
# `B.method(:new)` will return a method that will execute the stubbed version
-
# of the method on `A` since singleton methods on classes are in the lookup
-
# hierarchy.
-
#
-
# To do it properly, we need to find the original definition of `new` from `A`
-
# from _before_ `A` was stubbed, and we need to rebind it to `B` so that it will
-
# run with the proper `self`.
-
#
-
# That's what this method (together with `original_unbound_method_handle_from_ancestor_for`)
-
# does.
-
1
def original_method_handle_for(message)
-
unbound_method = superclass_proxy &&
-
superclass_proxy.original_unbound_method_handle_from_ancestor_for(message.to_sym)
-
-
return super unless unbound_method
-
unbound_method.bind(object)
-
end
-
-
1
protected
-
-
1
def original_unbound_method_handle_from_ancestor_for(message)
-
method_double = @method_doubles.fetch(message) do
-
# The fact that there is no method double for this message indicates
-
# that it has not been redefined by rspec-mocks. We need to continue
-
# looking up the ancestor chain.
-
return superclass_proxy &&
-
superclass_proxy.original_unbound_method_handle_from_ancestor_for(message)
-
end
-
-
method_double.original_method.unbind
-
end
-
-
1
def superclass_proxy
-
return @superclass_proxy if defined?(@superclass_proxy)
-
-
if (superclass = object.superclass)
-
@superclass_proxy = @source_space.superclass_proxy_for(superclass)
-
else
-
@superclass_proxy = nil
-
end
-
end
-
end
-
-
# @private
-
1
class PartialClassDoubleProxy < PartialDoubleProxy
-
1
include PartialClassDoubleProxyMethods
-
end
-
-
# @private
-
1
class ProxyForNil < PartialDoubleProxy
-
1
def initialize(order_group)
-
@warn_about_expectations = true
-
super(nil, order_group)
-
end
-
-
1
attr_accessor :warn_about_expectations
-
1
alias warn_about_expectations? warn_about_expectations
-
-
1
def add_message_expectation(method_name, opts={}, &block)
-
warn(method_name) if warn_about_expectations?
-
super
-
end
-
-
1
def add_negative_message_expectation(location, method_name, &implementation)
-
warn(method_name) if warn_about_expectations?
-
super
-
end
-
-
1
def add_stub(method_name, opts={}, &implementation)
-
warn(method_name) if warn_about_expectations?
-
super
-
end
-
-
1
private
-
-
1
def warn(method_name)
-
source = CallerFilter.first_non_rspec_line
-
Kernel.warn("An expectation of :#{method_name} was set on nil. Called from #{source}. Use allow_message_expectations_on_nil to disable warnings.")
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Allows a thread to lock out other threads from a critical section of code,
-
# while allowing the thread with the lock to reenter that section.
-
#
-
# Based on Monitor as of 2.2 -
-
# https://github.com/ruby/ruby/blob/eb7ddaa3a47bf48045d26c72eb0f263a53524ebc/lib/monitor.rb#L9
-
#
-
# Depends on Mutex, but Mutex is only available as part of core since 1.9.1:
-
# exists - http://ruby-doc.org/core-1.9.1/Mutex.html
-
# dne - http://ruby-doc.org/core-1.9.0/Mutex.html
-
#
-
# @private
-
1
class ReentrantMutex
-
1
def initialize
-
14
@owner = nil
-
14
@count = 0
-
14
@mutex = Mutex.new
-
end
-
-
1
def synchronize
-
enter
-
yield
-
ensure
-
exit
-
end
-
-
1
private
-
-
1
def enter
-
@mutex.lock if @owner != Thread.current
-
@owner = Thread.current
-
@count += 1
-
end
-
-
1
def exit
-
@count -= 1
-
return unless @count == 0
-
@owner = nil
-
@mutex.unlock
-
end
-
end
-
-
1
if defined? ::Mutex
-
# On 1.9 and up, this is in core, so we just use the real one
-
1
Mutex = ::Mutex
-
else # For 1.8.7
-
# :nocov:
-
skipped
RSpec::Support.require_rspec_mocks "mutex"
-
# :nocov:
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_mocks 'reentrant_mutex'
-
-
1
module RSpec
-
1
module Mocks
-
# @private
-
# Provides a default space implementation for outside
-
# the scope of an example. Called "root" because it serves
-
# as the root of the space stack.
-
1
class RootSpace
-
1
def proxy_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def any_instance_recorder_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def any_instance_proxy_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def register_constant_mutator(_mutator)
-
raise_lifecycle_message
-
end
-
-
1
def any_instance_recorders_from_ancestry_of(_object)
-
raise_lifecycle_message
-
end
-
-
1
def reset_all
-
end
-
-
1
def verify_all
-
end
-
-
1
def registered?(_object)
-
false
-
end
-
-
1
def superclass_proxy_for(*_args)
-
raise_lifecycle_message
-
end
-
-
1
def new_scope
-
7
Space.new
-
end
-
-
1
private
-
-
1
def raise_lifecycle_message
-
raise OutsideOfExampleError,
-
"The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported."
-
end
-
end
-
-
# @private
-
1
class Space
-
1
attr_reader :proxies, :any_instance_recorders, :proxy_mutex, :any_instance_mutex
-
-
1
def initialize
-
7
@proxies = {}
-
7
@any_instance_recorders = {}
-
7
@constant_mutators = []
-
7
@expectation_ordering = OrderGroup.new
-
7
@proxy_mutex = new_mutex
-
7
@any_instance_mutex = new_mutex
-
end
-
-
1
def new_scope
-
NestedSpace.new(self)
-
end
-
-
1
def verify_all
-
7
proxies.values.each { |proxy| proxy.verify }
-
7
any_instance_recorders.each_value { |recorder| recorder.verify }
-
end
-
-
1
def reset_all
-
7
proxies.each_value { |proxy| proxy.reset }
-
7
@constant_mutators.reverse.each { |mut| mut.idempotently_reset }
-
7
any_instance_recorders.each_value { |recorder| recorder.stop_all_observation! }
-
7
any_instance_recorders.clear
-
end
-
-
1
def register_constant_mutator(mutator)
-
@constant_mutators << mutator
-
end
-
-
1
def constant_mutator_for(name)
-
@constant_mutators.find { |m| m.full_constant_name == name }
-
end
-
-
1
def any_instance_recorder_for(klass, only_return_existing=false)
-
any_instance_mutex.synchronize do
-
id = klass.__id__
-
any_instance_recorders.fetch(id) do
-
return nil if only_return_existing
-
any_instance_recorder_not_found_for(id, klass)
-
end
-
end
-
end
-
-
1
def any_instance_proxy_for(klass)
-
AnyInstance::Proxy.new(any_instance_recorder_for(klass), proxies_of(klass))
-
end
-
-
1
def proxies_of(klass)
-
proxies.values.select { |proxy| klass === proxy.object }
-
end
-
-
1
def proxy_for(object)
-
proxy_mutex.synchronize do
-
id = id_for(object)
-
proxies.fetch(id) { proxy_not_found_for(id, object) }
-
end
-
end
-
-
1
def superclass_proxy_for(klass)
-
proxy_mutex.synchronize do
-
id = id_for(klass)
-
proxies.fetch(id) { superclass_proxy_not_found_for(id, klass) }
-
end
-
end
-
-
1
alias ensure_registered proxy_for
-
-
1
def registered?(object)
-
proxies.key?(id_for object)
-
end
-
-
1
def any_instance_recorders_from_ancestry_of(object)
-
# Optimization: `any_instance` is a feature we generally
-
# recommend not using, so we can often early exit here
-
# without doing an O(N) linear search over the number of
-
# ancestors in the object's class hierarchy.
-
return [] if any_instance_recorders.empty?
-
-
# We access the ancestors through the singleton class, to avoid calling
-
# `class` in case `class` has been stubbed.
-
(class << object; ancestors; end).map do |klass|
-
any_instance_recorders[klass.__id__]
-
end.compact
-
end
-
-
1
private
-
-
1
def new_mutex
-
14
Mocks::ReentrantMutex.new
-
end
-
-
1
def proxy_not_found_for(id, object)
-
proxies[id] = case object
-
when NilClass then ProxyForNil.new(@expectation_ordering)
-
when TestDouble then object.__build_mock_proxy_unless_expired(@expectation_ordering)
-
when Class
-
class_proxy_with_callback_verification_strategy(object, CallbackInvocationStrategy.new)
-
else
-
if RSpec::Mocks.configuration.verify_partial_doubles?
-
VerifyingPartialDoubleProxy.new(object, @expectation_ordering)
-
else
-
PartialDoubleProxy.new(object, @expectation_ordering)
-
end
-
end
-
end
-
-
1
def superclass_proxy_not_found_for(id, object)
-
raise "superclass_proxy_not_found_for called with something that is not a class" unless Class === object
-
proxies[id] = class_proxy_with_callback_verification_strategy(object, NoCallbackInvocationStrategy.new)
-
end
-
-
1
def class_proxy_with_callback_verification_strategy(object, strategy)
-
if RSpec::Mocks.configuration.verify_partial_doubles?
-
VerifyingPartialClassDoubleProxy.new(
-
self,
-
object,
-
@expectation_ordering,
-
strategy
-
)
-
else
-
PartialClassDoubleProxy.new(self, object, @expectation_ordering)
-
end
-
end
-
-
1
def any_instance_recorder_not_found_for(id, klass)
-
any_instance_recorders[id] = AnyInstance::Recorder.new(klass)
-
end
-
-
1
if defined?(::BasicObject) && !::BasicObject.method_defined?(:__id__) # for 1.9.2
-
require 'securerandom'
-
-
def id_for(object)
-
id = object.__id__
-
-
return id if object.equal?(::ObjectSpace._id2ref(id))
-
# this suggests that object.__id__ is proxying through to some wrapped object
-
-
object.instance_exec do
-
@__id_for_rspec_mocks_space ||= ::SecureRandom.uuid
-
end
-
end
-
else
-
1
def id_for(object)
-
object.__id__
-
end
-
end
-
end
-
-
# @private
-
1
class NestedSpace < Space
-
1
def initialize(parent)
-
@parent = parent
-
super()
-
end
-
-
1
def proxies_of(klass)
-
super + @parent.proxies_of(klass)
-
end
-
-
1
def constant_mutator_for(name)
-
super || @parent.constant_mutator_for(name)
-
end
-
-
1
def registered?(object)
-
super || @parent.registered?(object)
-
end
-
-
1
private
-
-
1
def proxy_not_found_for(id, object)
-
@parent.proxies[id] || super
-
end
-
-
1
def any_instance_recorder_not_found_for(id, klass)
-
@parent.any_instance_recorders[id] || super
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# @api private
-
# Provides methods for enabling and disabling the available syntaxes
-
# provided by rspec-mocks.
-
1
module Syntax
-
# @private
-
1
def self.warn_about_should!
-
1
@warn_about_should = true
-
end
-
-
# @private
-
1
def self.warn_unless_should_configured(method_name , replacement="the new `:expect` syntax or explicitly enable `:should`")
-
if @warn_about_should
-
RSpec.deprecate(
-
"Using `#{method_name}` from rspec-mocks' old `:should` syntax without explicitly enabling the syntax",
-
:replacement => replacement
-
)
-
-
@warn_about_should = false
-
end
-
end
-
-
# @api private
-
# Enables the should syntax (`dbl.stub`, `dbl.should_receive`, etc).
-
1
def self.enable_should(syntax_host=default_should_syntax_host)
-
1
@warn_about_should = false if syntax_host == default_should_syntax_host
-
1
return if should_enabled?(syntax_host)
-
-
1
syntax_host.class_exec do
-
1
def should_receive(message, opts={}, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.expect_message(self, message, opts, &block)
-
end
-
-
1
def should_not_receive(message, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.expect_message(self, message, {}, &block).never
-
end
-
-
1
def stub(message_or_hash, opts={}, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
if ::Hash === message_or_hash
-
message_or_hash.each { |message, value| stub(message).and_return value }
-
else
-
::RSpec::Mocks.allow_message(self, message_or_hash, opts, &block)
-
end
-
end
-
-
1
def unstub(message)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__, "`allow(...).to_receive(...).and_call_original` or explicitly enable `:should`")
-
::RSpec::Mocks.space.proxy_for(self).remove_stub(message)
-
end
-
-
1
def stub_chain(*chain, &blk)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks::StubChain.stub_chain_on(self, *chain, &blk)
-
end
-
-
1
def as_null_object
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
@_null_object = true
-
::RSpec::Mocks.space.proxy_for(self).as_null_object
-
end
-
-
1
def null_object?
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
defined?(@_null_object)
-
end
-
-
1
def received_message?(message, *args, &block)
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.space.proxy_for(self).received_message?(message, *args, &block)
-
end
-
-
1
unless Class.respond_to? :any_instance
-
1
Class.class_exec do
-
1
def any_instance
-
::RSpec::Mocks::Syntax.warn_unless_should_configured(__method__)
-
::RSpec::Mocks.space.any_instance_proxy_for(self)
-
end
-
end
-
end
-
end
-
end
-
-
# @api private
-
# Disables the should syntax (`dbl.stub`, `dbl.should_receive`, etc).
-
1
def self.disable_should(syntax_host=default_should_syntax_host)
-
return unless should_enabled?(syntax_host)
-
-
syntax_host.class_exec do
-
undef should_receive
-
undef should_not_receive
-
undef stub
-
undef unstub
-
undef stub_chain
-
undef as_null_object
-
undef null_object?
-
undef received_message?
-
end
-
-
Class.class_exec do
-
undef any_instance
-
end
-
end
-
-
# @api private
-
# Enables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc).
-
1
def self.enable_expect(syntax_host=::RSpec::Mocks::ExampleMethods)
-
1
return if expect_enabled?(syntax_host)
-
-
1
syntax_host.class_exec do
-
1
def receive(method_name, &block)
-
Matchers::Receive.new(method_name, block)
-
end
-
-
1
def receive_messages(message_return_value_hash)
-
matcher = Matchers::ReceiveMessages.new(message_return_value_hash)
-
matcher.warn_about_block if block_given?
-
matcher
-
end
-
-
1
def receive_message_chain(*messages, &block)
-
Matchers::ReceiveMessageChain.new(messages, &block)
-
end
-
-
1
def allow(target)
-
AllowanceTarget.new(target)
-
end
-
-
1
def expect_any_instance_of(klass)
-
AnyInstanceExpectationTarget.new(klass)
-
end
-
-
1
def allow_any_instance_of(klass)
-
AnyInstanceAllowanceTarget.new(klass)
-
end
-
end
-
-
1
RSpec::Mocks::ExampleMethods::ExpectHost.class_exec do
-
1
def expect(target)
-
ExpectationTarget.new(target)
-
end
-
end
-
end
-
-
# @api private
-
# Disables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc).
-
1
def self.disable_expect(syntax_host=::RSpec::Mocks::ExampleMethods)
-
return unless expect_enabled?(syntax_host)
-
-
syntax_host.class_exec do
-
undef receive
-
undef receive_messages
-
undef receive_message_chain
-
undef allow
-
undef expect_any_instance_of
-
undef allow_any_instance_of
-
end
-
-
RSpec::Mocks::ExampleMethods::ExpectHost.class_exec do
-
undef expect
-
end
-
end
-
-
# @api private
-
# Indicates whether or not the should syntax is enabled.
-
1
def self.should_enabled?(syntax_host=default_should_syntax_host)
-
1
syntax_host.method_defined?(:should_receive)
-
end
-
-
# @api private
-
# Indicates whether or not the expect syntax is enabled.
-
1
def self.expect_enabled?(syntax_host=::RSpec::Mocks::ExampleMethods)
-
1
syntax_host.method_defined?(:allow)
-
end
-
-
# @api private
-
# Determines where the methods like `should_receive`, and `stub` are added.
-
1
def self.default_should_syntax_host
-
# JRuby 1.7.4 introduces a regression whereby `defined?(::BasicObject) => nil`
-
# yet `BasicObject` still exists and patching onto ::Object breaks things
-
# e.g. SimpleDelegator expectations won't work
-
#
-
# See: https://github.com/jruby/jruby/issues/814
-
2
if defined?(JRUBY_VERSION) && JRUBY_VERSION == '1.7.4' && RUBY_VERSION.to_f > 1.8
-
return ::BasicObject
-
end
-
-
# On 1.8.7, Object.ancestors.last == Kernel but
-
# things blow up if we include `RSpec::Mocks::Methods`
-
# into Kernel...not sure why.
-
2
return Object unless defined?(::BasicObject)
-
-
# MacRuby has BasicObject but it's not the root class.
-
2
return Object unless Object.ancestors.last == ::BasicObject
-
-
2
::BasicObject
-
end
-
end
-
end
-
end
-
-
1
if defined?(BasicObject)
-
# The legacy `:should` syntax adds the following methods directly to
-
# `BasicObject` so that they are available off of any object. Note, however,
-
# that this syntax does not always play nice with delegate/proxy objects.
-
# We recommend you use the non-monkeypatching `:expect` syntax instead.
-
# @see Class
-
1
class BasicObject
-
# @method should_receive
-
# Sets an expectation that this object should receive a message before
-
# the end of the example.
-
#
-
# @example
-
# logger = double('logger')
-
# thing_that_logs = ThingThatLogs.new(logger)
-
# logger.should_receive(:log)
-
# thing_that_logs.do_something_that_logs_a_message
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#expect
-
-
# @method should_not_receive
-
# Sets and expectation that this object should _not_ receive a message
-
# during this example.
-
# @see RSpec::Mocks::ExampleMethods#expect
-
-
# @method stub
-
# Tells the object to respond to the message with the specified value.
-
#
-
# @example
-
# counter.stub(:count).and_return(37)
-
# counter.stub(:count => 37)
-
# counter.stub(:count) { 37 }
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#allow
-
-
# @method unstub
-
# Removes a stub. On a double, the object will no longer respond to
-
# `message`. On a real object, the original method (if it exists) is
-
# restored.
-
#
-
# This is rarely used, but can be useful when a stub is set up during a
-
# shared `before` hook for the common case, but you want to replace it
-
# for a special case.
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
-
# @method stub_chain
-
# @overload stub_chain(method1, method2)
-
# @overload stub_chain("method1.method2")
-
# @overload stub_chain(method1, method_to_value_hash)
-
#
-
# Stubs a chain of methods.
-
#
-
# ## Warning:
-
#
-
# Chains can be arbitrarily long, which makes it quite painless to
-
# violate the Law of Demeter in violent ways, so you should consider any
-
# use of `stub_chain` a code smell. Even though not all code smells
-
# indicate real problems (think fluent interfaces), `stub_chain` still
-
# results in brittle examples. For example, if you write
-
# `foo.stub_chain(:bar, :baz => 37)` in a spec and then the
-
# implementation calls `foo.baz.bar`, the stub will not work.
-
#
-
# @example
-
# double.stub_chain("foo.bar") { :baz }
-
# double.stub_chain(:foo, :bar => :baz)
-
# double.stub_chain(:foo, :bar) { :baz }
-
#
-
# # Given any of ^^ these three forms ^^:
-
# double.foo.bar # => :baz
-
#
-
# # Common use in Rails/ActiveRecord:
-
# Article.stub_chain("recent.published") { [Article.new] }
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#receive_message_chain
-
-
# @method as_null_object
-
# Tells the object to respond to all messages. If specific stub values
-
# are declared, they'll work as expected. If not, the receiver is
-
# returned.
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
-
# @method null_object?
-
# Returns true if this object has received `as_null_object`
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
end
-
end
-
-
# The legacy `:should` syntax adds the `any_instance` to `Class`.
-
# We generally recommend you use the newer `:expect` syntax instead,
-
# which allows you to stub any instance of a class using
-
# `allow_any_instance_of(klass)` or mock any instance using
-
# `expect_any_instance_of(klass)`.
-
# @see BasicObject
-
1
class Class
-
# @method any_instance
-
# Used to set stubs and message expectations on any instance of a given
-
# class. Returns a [Recorder](Recorder), which records messages like
-
# `stub` and `should_receive` for later playback on instances of the
-
# class.
-
#
-
# @example
-
# Car.any_instance.should_receive(:go)
-
# race = Race.new
-
# race.cars << Car.new
-
# race.go # assuming this delegates to all of its cars
-
# # this example would pass
-
#
-
# Account.any_instance.stub(:balance) { Money.new(:USD, 25) }
-
# Account.new.balance # => Money.new(:USD, 25))
-
#
-
# @return [Recorder]
-
#
-
# @note This is only available when you have enabled the `should` syntax.
-
# @see RSpec::Mocks::ExampleMethods#expect_any_instance_of
-
# @see RSpec::Mocks::ExampleMethods#allow_any_instance_of
-
end
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class TargetBase
-
1
def initialize(target)
-
@target = target
-
end
-
-
1
def self.delegate_to(matcher_method)
-
4
define_method(:to) do |matcher, &block|
-
unless matcher_allowed?(matcher)
-
raise_unsupported_matcher(:to, matcher)
-
end
-
define_matcher(matcher, matcher_method, &block)
-
end
-
end
-
-
1
def self.delegate_not_to(matcher_method, options={})
-
4
method_name = options.fetch(:from)
-
4
define_method(method_name) do |matcher, &block|
-
case matcher
-
when Matchers::Receive
-
define_matcher(matcher, matcher_method, &block)
-
when Matchers::ReceiveMessages, Matchers::ReceiveMessageChain
-
raise_negation_unsupported(method_name, matcher)
-
else
-
raise_unsupported_matcher(method_name, matcher)
-
end
-
end
-
end
-
-
1
def self.disallow_negation(method_name)
-
4
define_method(method_name) do |matcher, *_args|
-
raise_negation_unsupported(method_name, matcher)
-
end
-
end
-
-
1
private
-
-
1
def matcher_allowed?(matcher)
-
matcher.class.name.start_with?("RSpec::Mocks::Matchers".freeze)
-
end
-
-
1
def define_matcher(matcher, name, &block)
-
matcher.__send__(name, @target, &block)
-
end
-
-
1
def raise_unsupported_matcher(method_name, matcher)
-
raise UnsupportedMatcherError,
-
"only the `receive` or `receive_messages` matchers are supported " \
-
"with `#{expression}(...).#{method_name}`, but you have provided: #{matcher}"
-
end
-
-
1
def raise_negation_unsupported(method_name, matcher)
-
raise NegationUnsupportedError,
-
"`#{expression}(...).#{method_name} #{matcher.name}` is not supported since it " \
-
"doesn't really make sense. What would it even mean?"
-
end
-
-
1
def expression
-
self.class::EXPRESSION
-
end
-
end
-
-
# @private
-
1
class AllowanceTarget < TargetBase
-
1
EXPRESSION = :allow
-
1
delegate_to :setup_allowance
-
1
disallow_negation :not_to
-
1
disallow_negation :to_not
-
end
-
-
# @private
-
1
class ExpectationTarget < TargetBase
-
1
EXPRESSION = :expect
-
1
delegate_to :setup_expectation
-
1
delegate_not_to :setup_negative_expectation, :from => :not_to
-
1
delegate_not_to :setup_negative_expectation, :from => :to_not
-
end
-
-
# @private
-
1
class AnyInstanceAllowanceTarget < TargetBase
-
1
EXPRESSION = :allow_any_instance_of
-
1
delegate_to :setup_any_instance_allowance
-
1
disallow_negation :not_to
-
1
disallow_negation :to_not
-
end
-
-
# @private
-
1
class AnyInstanceExpectationTarget < TargetBase
-
1
EXPRESSION = :expect_any_instance_of
-
1
delegate_to :setup_any_instance_expectation
-
1
delegate_not_to :setup_any_instance_negative_expectation, :from => :not_to
-
1
delegate_not_to :setup_any_instance_negative_expectation, :from => :to_not
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Implements the methods needed for a pure test double. RSpec::Mocks::Double
-
# includes this module, and it is provided for cases where you want a
-
# pure test double without subclassing RSpec::Mocks::Double.
-
1
module TestDouble
-
# Creates a new test double with a `name` (that will be used in error
-
# messages only)
-
1
def initialize(name=nil, stubs={})
-
@__expired = false
-
if Hash === name && stubs.empty?
-
stubs = name
-
@name = nil
-
else
-
@name = name
-
end
-
assign_stubs(stubs)
-
end
-
-
# Tells the object to respond to all messages. If specific stub values
-
# are declared, they'll work as expected. If not, the receiver is
-
# returned.
-
1
def as_null_object
-
__mock_proxy.as_null_object
-
end
-
-
# Returns true if this object has received `as_null_object`
-
1
def null_object?
-
__mock_proxy.null_object?
-
end
-
-
# This allows for comparing the mock to other objects that proxy such as
-
# ActiveRecords belongs_to proxy objects. By making the other object run
-
# the comparison, we're sure the call gets delegated to the proxy
-
# target.
-
1
def ==(other)
-
other == __mock_proxy
-
end
-
-
# @private
-
1
def inspect
-
TestDoubleFormatter.format(self)
-
end
-
-
# @private
-
1
def to_s
-
inspect.gsub('<', '[').gsub('>', ']')
-
end
-
-
# @private
-
1
def respond_to?(message, incl_private=false)
-
__mock_proxy.null_object? ? true : super
-
end
-
-
# @private
-
1
def __build_mock_proxy_unless_expired(order_group)
-
__raise_expired_error || __build_mock_proxy(order_group)
-
end
-
-
# @private
-
1
def __disallow_further_usage!
-
@__expired = true
-
end
-
-
# Override for default freeze implementation to prevent freezing of test
-
# doubles.
-
1
def freeze
-
RSpec.warn_with("WARNING: you attempted to freeze a test double. This is explicitly a no-op as freezing doubles can lead to undesired behaviour when resetting tests.")
-
end
-
-
1
private
-
-
1
def method_missing(message, *args, &block)
-
proxy = __mock_proxy
-
proxy.record_message_received(message, *args, &block)
-
-
if proxy.null_object?
-
case message
-
when :to_int then return 0
-
when :to_a, :to_ary then return nil
-
when :to_str then return to_s
-
else return self
-
end
-
end
-
-
# Defined private and protected methods will still trigger `method_missing`
-
# when called publicly. We want ruby's method visibility error to get raised,
-
# so we simply delegate to `super` in that case.
-
# ...well, we would delegate to `super`, but there's a JRuby
-
# bug, so we raise our own visibility error instead:
-
# https://github.com/jruby/jruby/issues/1398
-
visibility = proxy.visibility_for(message)
-
if visibility == :private || visibility == :protected
-
ErrorGenerator.new(self).raise_non_public_error(
-
message, visibility
-
)
-
end
-
-
# Required wrapping doubles in an Array on Ruby 1.9.2
-
raise NoMethodError if [:to_a, :to_ary].include? message
-
proxy.raise_unexpected_message_error(message, args)
-
end
-
-
1
def assign_stubs(stubs)
-
stubs.each_pair do |message, response|
-
__mock_proxy.add_simple_stub(message, response)
-
end
-
end
-
-
1
def __mock_proxy
-
::RSpec::Mocks.space.proxy_for(self)
-
end
-
-
1
def __build_mock_proxy(order_group)
-
TestDoubleProxy.new(self, order_group)
-
end
-
-
1
def __raise_expired_error
-
return false unless @__expired
-
ErrorGenerator.new(self).raise_expired_test_double_error
-
end
-
-
1
def initialize_copy(other)
-
as_null_object if other.null_object?
-
super
-
end
-
end
-
-
# A generic test double object. `double`, `instance_double` and friends
-
# return an instance of this.
-
1
class Double
-
1
include TestDouble
-
end
-
-
# @private
-
1
module TestDoubleFormatter
-
1
def self.format(dbl, unwrap=false)
-
format = "#{type_desc(dbl)}#{verified_module_desc(dbl)} #{name_desc(dbl)}"
-
return format if unwrap
-
"#<#{format}>"
-
end
-
-
1
class << self
-
1
private
-
-
1
def type_desc(dbl)
-
case dbl
-
when InstanceVerifyingDouble then "InstanceDouble"
-
when ClassVerifyingDouble then "ClassDouble"
-
when ObjectVerifyingDouble then "ObjectDouble"
-
else "Double"
-
end
-
end
-
-
# @private
-
1
IVAR_GET = Object.instance_method(:instance_variable_get)
-
-
1
def verified_module_desc(dbl)
-
return nil unless VerifyingDouble === dbl
-
"(#{IVAR_GET.bind(dbl).call(:@doubled_module).description})"
-
end
-
-
1
def name_desc(dbl)
-
return "(anonymous)" unless (name = IVAR_GET.bind(dbl).call(:@name))
-
name.inspect
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_mocks 'verifying_proxy'
-
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
module VerifyingDouble
-
1
def respond_to?(message, include_private=false)
-
return super unless null_object?
-
-
method_ref = __mock_proxy.method_reference[message]
-
-
case method_ref.visibility
-
when :public then true
-
when :private then include_private
-
when :protected then include_private || RUBY_VERSION.to_f < 2.0
-
else !method_ref.unimplemented?
-
end
-
end
-
-
1
def method_missing(message, *args, &block)
-
# Null object conditional is an optimization. If not a null object,
-
# validity of method expectations will have been checked at definition
-
# time.
-
if null_object?
-
if @__sending_message == message
-
__mock_proxy.ensure_implemented(message)
-
else
-
__mock_proxy.ensure_publicly_implemented(message, self)
-
end
-
-
__mock_proxy.validate_arguments!(message, args)
-
end
-
-
super
-
end
-
-
# @private
-
1
module SilentIO
-
1
def self.method_missing(*); end
-
1
def self.respond_to?(*)
-
1
true
-
end
-
end
-
-
# Redefining `__send__` causes ruby to issue a warning.
-
1
old, $stderr = $stderr, SilentIO
-
1
def __send__(name, *args, &block)
-
@__sending_message = name
-
super
-
ensure
-
@__sending_message = nil
-
end
-
1
$stderr = old
-
-
1
def send(name, *args, &block)
-
__send__(name, *args, &block)
-
end
-
-
1
def initialize(doubled_module, *args)
-
@doubled_module = doubled_module
-
-
possible_name = args.first
-
name = if String === possible_name || Symbol === possible_name
-
args.shift
-
end
-
-
super(name, *args)
-
@__sending_message = nil
-
end
-
end
-
-
# A mock providing a custom proxy that can verify the validity of any
-
# method stubs or expectations against the public instance methods of the
-
# given class.
-
#
-
# @private
-
1
class InstanceVerifyingDouble
-
1
include TestDouble
-
1
include VerifyingDouble
-
-
1
def __build_mock_proxy(order_group)
-
VerifyingProxy.new(self, order_group,
-
@doubled_module,
-
InstanceMethodReference
-
)
-
end
-
end
-
-
# An awkward module necessary because we cannot otherwise have
-
# ClassVerifyingDouble inherit from Module and still share these methods.
-
#
-
# @private
-
1
module ObjectVerifyingDoubleMethods
-
1
include TestDouble
-
1
include VerifyingDouble
-
-
1
def as_stubbed_const(options={})
-
ConstantMutator.stub(@doubled_module.const_to_replace, self, options)
-
self
-
end
-
-
1
private
-
-
1
def __build_mock_proxy(order_group)
-
VerifyingProxy.new(self, order_group,
-
@doubled_module,
-
ObjectMethodReference
-
)
-
end
-
end
-
-
# Similar to an InstanceVerifyingDouble, except that it verifies against
-
# public methods of the given object.
-
#
-
# @private
-
1
class ObjectVerifyingDouble
-
1
include ObjectVerifyingDoubleMethods
-
end
-
-
# Effectively the same as an ObjectVerifyingDouble (since a class is a type
-
# of object), except with Module in the inheritance chain so that
-
# transferring nested constants to work.
-
#
-
# @private
-
1
class ClassVerifyingDouble < Module
-
1
include ObjectVerifyingDoubleMethods
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_support 'method_signature_verifier'
-
-
1
module RSpec
-
1
module Mocks
-
# A message expectation that knows about the real implementation of the
-
# message being expected, so that it can verify that any expectations
-
# have the valid arguments.
-
# @api private
-
1
class VerifyingMessageExpectation < MessageExpectation
-
# A level of indirection is used here rather than just passing in the
-
# method itself, since method look up is expensive and we only want to
-
# do it if actually needed.
-
#
-
# Conceptually the method reference makes more sense as a constructor
-
# argument since it should be immutable, but it is significantly more
-
# straight forward to build the object in pieces so for now it stays as
-
# an accessor.
-
1
attr_accessor :method_reference
-
-
1
def initialize(*args)
-
super
-
end
-
-
# @private
-
1
def with(*args, &block)
-
super(*args, &block).tap do
-
validate_expected_arguments! do |signature|
-
example_call_site_args = [:an_arg] * signature.min_non_kw_args
-
example_call_site_args << :kw_args_hash if signature.required_kw_args.any?
-
@argument_list_matcher.resolve_expected_args_based_on(example_call_site_args)
-
end
-
end
-
end
-
-
1
private
-
-
1
def validate_expected_arguments!
-
return if method_reference.nil?
-
-
method_reference.with_signature do |signature|
-
args = yield signature
-
verifier = Support::LooseSignatureVerifier.new(signature, args)
-
-
unless verifier.valid?
-
# Fail fast is required, otherwise the message expecation will fail
-
# as well ("expected method not called") and clobber this one.
-
@failed_fast = true
-
@error_generator.raise_invalid_arguments_error(verifier)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
RSpec::Support.require_rspec_mocks 'verifying_message_expecation'
-
1
RSpec::Support.require_rspec_mocks 'method_reference'
-
-
1
module RSpec
-
1
module Mocks
-
# @private
-
1
class CallbackInvocationStrategy
-
1
def call(doubled_module)
-
RSpec::Mocks.configuration.verifying_double_callbacks.each do |block|
-
block.call doubled_module
-
end
-
end
-
end
-
-
# @private
-
1
class NoCallbackInvocationStrategy
-
1
def call(_doubled_module)
-
end
-
end
-
-
# @private
-
1
module VerifyingProxyMethods
-
1
def add_stub(method_name, opts={}, &implementation)
-
ensure_implemented(method_name)
-
super
-
end
-
-
1
def add_simple_stub(method_name, *args)
-
ensure_implemented(method_name)
-
super
-
end
-
-
1
def add_message_expectation(method_name, opts={}, &block)
-
ensure_implemented(method_name)
-
super
-
end
-
-
1
def ensure_implemented(method_name)
-
return unless method_reference[method_name].unimplemented?
-
-
@error_generator.raise_unimplemented_error(
-
@doubled_module,
-
method_name,
-
@object
-
)
-
end
-
-
1
def ensure_publicly_implemented(method_name, _object)
-
ensure_implemented(method_name)
-
visibility = method_reference[method_name].visibility
-
-
return if visibility == :public
-
@error_generator.raise_non_public_error(method_name, visibility)
-
end
-
end
-
-
# A verifying proxy mostly acts like a normal proxy, except that it
-
# contains extra logic to try and determine the validity of any expectation
-
# set on it. This includes whether or not methods have been defined and the
-
# validatiy of arguments on method calls.
-
#
-
# In all other ways this behaves like a normal proxy. It only adds the
-
# verification behaviour to specific methods then delegates to the parent
-
# implementation.
-
#
-
# These checks are only activated if the doubled class has already been
-
# loaded, otherwise they are disabled. This allows for testing in
-
# isolation.
-
#
-
# @private
-
1
class VerifyingProxy < TestDoubleProxy
-
1
include VerifyingProxyMethods
-
-
1
def initialize(object, order_group, doubled_module, method_reference_class)
-
super(object, order_group)
-
@object = object
-
@doubled_module = doubled_module
-
@method_reference_class = method_reference_class
-
-
# A custom method double is required to pass through a way to lookup
-
# methods to determine their parameters. This is only relevant if the doubled
-
# class is loaded.
-
@method_doubles = Hash.new do |h, k|
-
h[k] = VerifyingMethodDouble.new(@object, k, self, method_reference[k])
-
end
-
end
-
-
1
def method_reference
-
@method_reference ||= Hash.new do |h, k|
-
h[k] = @method_reference_class.for(@doubled_module, k)
-
end
-
end
-
-
1
def visibility_for(method_name)
-
method_reference[method_name].visibility
-
end
-
-
1
def validate_arguments!(method_name, args)
-
@method_doubles[method_name].validate_arguments!(args)
-
end
-
end
-
-
# @private
-
1
DEFAULT_CALLBACK_INVOCATION_STRATEGY = CallbackInvocationStrategy.new
-
-
# @private
-
1
class VerifyingPartialDoubleProxy < PartialDoubleProxy
-
1
include VerifyingProxyMethods
-
-
1
def initialize(object, expectation_ordering, optional_callback_invocation_strategy=DEFAULT_CALLBACK_INVOCATION_STRATEGY)
-
super(object, expectation_ordering)
-
@doubled_module = DirectObjectReference.new(object)
-
-
# A custom method double is required to pass through a way to lookup
-
# methods to determine their parameters.
-
@method_doubles = Hash.new do |h, k|
-
h[k] = VerifyingExistingMethodDouble.for(object, k, self)
-
end
-
-
optional_callback_invocation_strategy.call(@doubled_module)
-
end
-
-
1
def method_reference
-
@method_doubles
-
end
-
end
-
-
# @private
-
1
class VerifyingPartialClassDoubleProxy < VerifyingPartialDoubleProxy
-
1
include PartialClassDoubleProxyMethods
-
end
-
-
# @private
-
1
class VerifyingMethodDouble < MethodDouble
-
1
def initialize(object, method_name, proxy, method_reference)
-
super(object, method_name, proxy)
-
@method_reference = method_reference
-
end
-
-
1
def message_expectation_class
-
VerifyingMessageExpectation
-
end
-
-
1
def add_expectation(*args, &block)
-
# explict params necessary for 1.8.7 see #626
-
super(*args, &block).tap { |x| x.method_reference = @method_reference }
-
end
-
-
1
def add_stub(*args, &block)
-
# explict params necessary for 1.8.7 see #626
-
super(*args, &block).tap { |x| x.method_reference = @method_reference }
-
end
-
-
1
def proxy_method_invoked(obj, *args, &block)
-
validate_arguments!(args)
-
super
-
end
-
-
1
def validate_arguments!(actual_args)
-
@method_reference.with_signature do |signature|
-
verifier = Support::StrictSignatureVerifier.new(signature, actual_args)
-
raise ArgumentError, verifier.error_message unless verifier.valid?
-
end
-
end
-
end
-
-
# A VerifyingMethodDouble fetches the method to verify against from the
-
# original object, using a MethodReference. This works for pure doubles,
-
# but when the original object is itself the one being modified we need to
-
# collapse the reference and the method double into a single object so that
-
# we can access the original pristine method definition.
-
#
-
# @private
-
1
class VerifyingExistingMethodDouble < VerifyingMethodDouble
-
1
def initialize(object, method_name, proxy)
-
super(object, method_name, proxy, self)
-
-
@valid_method = object.respond_to?(method_name, true)
-
-
# Trigger an eager find of the original method since if we find it any
-
# later we end up getting a stubbed method with incorrect arity.
-
save_original_implementation_callable!
-
end
-
-
1
def with_signature
-
yield Support::MethodSignature.new(original_implementation_callable)
-
end
-
-
1
def unimplemented?
-
!@valid_method
-
end
-
-
1
def self.for(object, method_name, proxy)
-
if ClassNewMethodReference.applies_to?(method_name) { object }
-
VerifyingExistingClassNewMethodDouble
-
else
-
self
-
end.new(object, method_name, proxy)
-
end
-
end
-
-
# Used in place of a `VerifyingExistingMethodDouble` for the specific case
-
# of mocking or stubbing a `new` method on a class. In this case, we substitute
-
# the method signature from `#initialize` since new's signature is just `*args`.
-
#
-
# @private
-
1
class VerifyingExistingClassNewMethodDouble < VerifyingExistingMethodDouble
-
1
def with_signature
-
yield Support::MethodSignature.new(object.instance_method(:initialize))
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Mocks
-
# Version information for RSpec mocks.
-
1
module Version
-
# Version of RSpec mocks currently in use in SemVer format.
-
1
STRING = '3.3.2'
-
end
-
end
-
end
-
1
require 'rspec/core'
-
1
require 'rails/version'
-
-
# Load any of our adapters and extensions early in the process
-
1
require 'rspec/rails/adapters'
-
1
require 'rspec/rails/extensions'
-
-
# Load the rspec-rails parts
-
1
require 'rspec/rails/view_rendering'
-
1
require 'rspec/rails/matchers'
-
1
require 'rspec/rails/fixture_support'
-
1
require 'rspec/rails/example'
-
1
require 'rspec/rails/vendor/capybara'
-
1
require 'rspec/rails/configuration'
-
1
require 'rspec/rails/active_record'
-
1
require 'rspec/rails/feature_check'
-
1
module RSpec
-
1
module Rails
-
# Fake class to document RSpec ActiveRecord configuration options. In practice,
-
# these are dynamically added to the normal RSpec configuration object.
-
1
class ActiveRecordConfiguration
-
# @private
-
1
def self.initialize_activerecord_configuration(config)
-
1
config.before :suite do
-
# This allows dynamic columns etc to be used on ActiveRecord models when creating instance_doubles
-
1
if defined?(ActiveRecord) && defined?(::RSpec::Mocks)
-
1
::RSpec::Mocks.configuration.when_declaring_verifying_double do |possible_model|
-
target = possible_model.target
-
-
if Class === target && ActiveRecord::Base > target && !target.abstract_class?
-
target.define_attribute_methods
-
end
-
end
-
end
-
end
-
end
-
-
1
initialize_activerecord_configuration RSpec.configuration
-
end
-
end
-
end
-
1
require 'delegate'
-
1
require 'active_support'
-
1
require 'active_support/concern'
-
1
require 'active_support/core_ext/string'
-
-
1
module RSpec
-
1
module Rails
-
# @private
-
1
def self.disable_testunit_autorun
-
# `Test::Unit::AutoRunner.need_auto_run=` was introduced to the test-unit
-
# gem in version 2.4.9. Previous to this version `Test::Unit.run=` was
-
# used. The implementation of test-unit included with Ruby has neither
-
# method.
-
if defined?(Test::Unit::AutoRunner.need_auto_run = ())
-
Test::Unit::AutoRunner.need_auto_run = false
-
elsif defined?(Test::Unit.run = ())
-
Test::Unit.run = false
-
end
-
end
-
1
private_class_method :disable_testunit_autorun
-
-
1
if ::Rails::VERSION::STRING >= '4.1.0'
-
1
if defined?(Kernel.gem)
-
1
gem 'minitest'
-
else
-
require 'minitest'
-
end
-
1
require 'minitest/assertions'
-
# Constant aliased to either Minitest or TestUnit, depending on what is
-
# loaded.
-
1
Assertions = Minitest::Assertions
-
elsif RUBY_VERSION >= '2.2.0'
-
# Minitest / TestUnit has been removed from ruby core. However, we are
-
# on an old Rails version and must load the appropriate gem
-
if ::Rails::VERSION::STRING >= '4.0.0'
-
# ActiveSupport 4.0.x has the minitest '~> 4.2' gem as a dependency
-
# This gem has no `lib/minitest.rb` file.
-
gem 'minitest' if defined?(Kernel.gem)
-
require 'minitest/unit'
-
Assertions = MiniTest::Assertions
-
elsif ::Rails::VERSION::STRING >= '3.2.21'
-
# TODO: Change the above check to >= '3.2.22' once it's released
-
begin
-
# Test::Unit "helpfully" sets up autoload for its `AutoRunner`.
-
# While we do not reference it directly, when we load the `TestCase`
-
# classes from AS (ActiveSupport), AS kindly references `AutoRunner`
-
# for everyone.
-
#
-
# To handle this we need to pre-emptively load 'test/unit' and make
-
# sure the version installed has `AutoRunner` (the 3.x line does to
-
# date). If so, we turn the auto runner off.
-
require 'test/unit'
-
require 'test/unit/assertions'
-
disable_testunit_autorun
-
rescue LoadError => e
-
raise LoadError, <<-ERR.squish, e.backtrace
-
Ruby 2.2+ has removed test/unit from the core library. Rails
-
requires this as a dependency. Please add test-unit gem to your
-
Gemfile: `gem 'test-unit', '~> 3.0'` (#{e.message})"
-
ERR
-
end
-
Assertions = Test::Unit::Assertions
-
else
-
abort <<-MSG.squish
-
Ruby 2.2+ is not supported on Rails #{::Rails::VERSION::STRING}.
-
Check the Rails release notes for the appropriate update with
-
support.
-
MSG
-
end
-
else
-
begin
-
require 'test/unit/assertions'
-
rescue LoadError
-
# work around for Rubinius not having a std std-lib
-
require 'rubysl-test-unit' if defined?(RUBY_ENGINE) && RUBY_ENGINE == 'rbx'
-
require 'test/unit/assertions'
-
end
-
# Turn off test unit's auto runner for those using the gem
-
disable_testunit_autorun
-
# Constant aliased to either Minitest or TestUnit, depending on what is
-
# loaded.
-
Assertions = Test::Unit::Assertions
-
end
-
-
# @private
-
1
class AssertionDelegator < Module
-
1
def initialize(*assertion_modules)
-
2
assertion_class = Class.new(SimpleDelegator) do
-
2
include ::RSpec::Rails::Assertions
-
2
include ::RSpec::Rails::MinitestCounters
-
4
assertion_modules.each { |mod| include mod }
-
end
-
-
2
super() do
-
2
define_method :build_assertion_instance do
-
7
assertion_class.new(self)
-
end
-
-
2
def assertion_instance
-
7
@assertion_instance ||= build_assertion_instance
-
end
-
-
2
assertion_modules.each do |mod|
-
2
mod.public_instance_methods.each do |method|
-
10
next if method == :method_missing || method == "method_missing"
-
8
define_method(method.to_sym) do |*args, &block|
-
assertion_instance.send(method.to_sym, *args, &block)
-
end
-
end
-
end
-
end
-
end
-
end
-
-
# Adapts example groups for `Minitest::Test::LifecycleHooks`
-
#
-
# @private
-
1
module MinitestLifecycleAdapter
-
1
extend ActiveSupport::Concern
-
-
1
included do |group|
-
8
group.before { after_setup }
-
8
group.after { before_teardown }
-
-
1
group.around do |example|
-
7
before_setup
-
7
example.run
-
7
after_teardown
-
end
-
end
-
-
1
def before_setup
-
end
-
-
1
def after_setup
-
end
-
-
1
def before_teardown
-
end
-
-
1
def after_teardown
-
end
-
end
-
-
# @private
-
1
module MinitestCounters
-
1
attr_writer :assertions
-
1
def assertions
-
@assertions ||= 0
-
end
-
end
-
-
# @private
-
1
module SetupAndTeardownAdapter
-
1
extend ActiveSupport::Concern
-
-
1
module ClassMethods
-
# Wraps `setup` calls from within Rails' testing framework in `before`
-
# hooks.
-
1
def setup(*methods)
-
3
methods.each do |method|
-
4
if method.to_s =~ /^setup_(with_controller|fixtures|controller_request_and_response)$/
-
8
prepend_before { __send__ method }
-
else
-
24
before { __send__ method }
-
end
-
end
-
end
-
-
# @api private
-
#
-
# Wraps `teardown` calls from within Rails' testing framework in
-
# `after` hooks.
-
1
def teardown(*methods)
-
9
methods.each { |method| after { __send__ method } }
-
end
-
end
-
-
1
def initialize(*args)
-
11
super
-
11
@example = nil
-
end
-
-
1
def method_name
-
14
@example
-
end
-
end
-
-
# @private
-
1
module MinitestAssertionAdapter
-
1
extend ActiveSupport::Concern
-
-
# @private
-
1
module ClassMethods
-
# Returns the names of assertion methods that we want to expose to
-
# examples without exposing non-assertion methods in Test::Unit or
-
# Minitest.
-
1
def assertion_method_names
-
1
methods = ::RSpec::Rails::Assertions.
-
public_instance_methods.
-
select do |m|
-
45
m.to_s =~ /^(assert|flunk|refute)/
-
end
-
1
methods + [:build_message]
-
end
-
-
1
def define_assertion_delegators
-
1
assertion_method_names.each do |m|
-
35
define_method(m.to_sym) do |*args, &block|
-
assertion_delegator.send(m.to_sym, *args, &block)
-
end
-
end
-
end
-
end
-
-
1
class AssertionDelegator
-
1
include ::RSpec::Rails::Assertions
-
1
include ::RSpec::Rails::MinitestCounters
-
end
-
-
1
def assertion_delegator
-
@assertion_delegator ||= AssertionDelegator.new
-
end
-
-
1
included do
-
1
define_assertion_delegators
-
end
-
end
-
-
# Backwards compatibility. It's unlikely that anyone is using this
-
# constant, but we had forgotten to mark it as `@private` earlier
-
#
-
# @private
-
1
TestUnitAssertionAdapter = MinitestAssertionAdapter
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# Fake class to document RSpec Rails configuration options. In practice,
-
# these are dynamically added to the normal RSpec configuration object.
-
1
class Configuration
-
# @!method infer_spec_type_from_file_location!
-
# Automatically tag specs in conventional directories with matching `type`
-
# metadata so that they have relevant helpers available to them. See
-
# `RSpec::Rails::DIRECTORY_MAPPINGS` for details on which metadata is
-
# applied to each directory.
-
-
# @!method render_views=(val)
-
#
-
# When set to `true`, controller specs will render the relevant view as
-
# well. Defaults to `false`.
-
-
# @!method render_views(val)
-
# Enables view rendering for controllers specs.
-
-
# @!method render_views?
-
# Reader for currently value of `render_views` setting.
-
end
-
-
# Mappings used by `infer_spec_type_from_file_location!`.
-
#
-
# @api private
-
1
DIRECTORY_MAPPINGS = {
-
:controller => %w[spec controllers],
-
:helper => %w[spec helpers],
-
:job => %w[spec jobs],
-
:mailer => %w[spec mailers],
-
:model => %w[spec models],
-
:request => %w[spec (requests|integration|api)],
-
:routing => %w[spec routing],
-
:view => %w[spec views],
-
:feature => %w[spec features]
-
}
-
-
# @private
-
1
def self.initialize_configuration(config)
-
1
config.backtrace_exclusion_patterns << /vendor\//
-
1
config.backtrace_exclusion_patterns << %r{ lib/rspec/rails }
-
-
1
config.include RSpec::Rails::ControllerExampleGroup, :type => :controller
-
1
config.include RSpec::Rails::HelperExampleGroup, :type => :helper
-
1
config.include RSpec::Rails::ModelExampleGroup, :type => :model
-
1
config.include RSpec::Rails::RequestExampleGroup, :type => :request
-
1
config.include RSpec::Rails::RoutingExampleGroup, :type => :routing
-
1
config.include RSpec::Rails::ViewExampleGroup, :type => :view
-
1
config.include RSpec::Rails::FeatureExampleGroup, :type => :feature
-
-
1
if defined?(ActionMailer)
-
1
config.include RSpec::Rails::MailerExampleGroup, :type => :mailer
-
end
-
-
1
if defined?(ActiveJob)
-
1
config.include RSpec::Rails::JobExampleGroup, :type => :job
-
end
-
-
# controller settings
-
1
config.add_setting :infer_base_class_for_anonymous_controllers, :default => true
-
-
# fixture support
-
1
config.add_setting :use_transactional_fixtures, :alias_with => :use_transactional_examples
-
1
config.add_setting :use_instantiated_fixtures
-
1
config.add_setting :global_fixtures
-
1
config.add_setting :fixture_path
-
1
config.include RSpec::Rails::FixtureSupport, :use_fixtures
-
-
# We'll need to create a deprecated module in order to properly report to
-
# gems / projects which are relying on this being loaded globally.
-
#
-
# See rspec/rspec-rails#1355 for history
-
#
-
# @deprecated Include `RSpec::Rails::RailsExampleGroup` or
-
# `RSpec::Rails::FixtureSupport` directly instead
-
1
config.include RSpec::Rails::FixtureSupport
-
-
# This allows us to expose `render_views` as a config option even though it
-
# breaks the convention of other options by using `render_views` as a
-
# command (i.e. `render_views = true`), where it would normally be used
-
# as a getter. This makes it easier for rspec-rails users because we use
-
# `render_views` directly in example groups, so this aligns the two APIs,
-
# but requires this workaround:
-
1
config.add_setting :rendering_views, :default => false
-
-
1
config.instance_exec do
-
1
def render_views=(val)
-
self.rendering_views = val
-
end
-
-
1
def render_views
-
self.rendering_views = true
-
end
-
-
1
def render_views?
-
14
rendering_views
-
end
-
-
1
def infer_spec_type_from_file_location!
-
1
DIRECTORY_MAPPINGS.each do |type, dir_parts|
-
9
escaped_path = Regexp.compile(dir_parts.join('[\\\/]') + '[\\\/]')
-
9
define_derived_metadata(:file_path => escaped_path) do |metadata|
-
9
metadata[:type] ||= type
-
end
-
end
-
end
-
end
-
end
-
-
1
initialize_configuration RSpec.configuration
-
end
-
end
-
1
require 'rspec/rails/example/rails_example_group'
-
1
require 'rspec/rails/example/controller_example_group'
-
1
require 'rspec/rails/example/request_example_group'
-
1
require 'rspec/rails/example/helper_example_group'
-
1
require 'rspec/rails/example/view_example_group'
-
1
require 'rspec/rails/example/mailer_example_group'
-
1
require 'rspec/rails/example/routing_example_group'
-
1
require 'rspec/rails/example/model_example_group'
-
1
require 'rspec/rails/example/job_example_group'
-
1
require 'rspec/rails/example/feature_example_group'
-
1
module RSpec
-
1
module Rails
-
# @private
-
1
ControllerAssertionDelegator = RSpec::Rails::AssertionDelegator.new(
-
ActionDispatch::Assertions::RoutingAssertions
-
)
-
-
# Container module for controller spec functionality.
-
1
module ControllerExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
1
include ActionController::TestCase::Behavior
-
1
include RSpec::Rails::ViewRendering
-
1
include RSpec::Rails::Matchers::RedirectTo
-
1
include RSpec::Rails::Matchers::RenderTemplate
-
1
include RSpec::Rails::Matchers::RoutingMatchers
-
1
include ControllerAssertionDelegator
-
-
# Class-level DSL for controller specs.
-
1
module ClassMethods
-
# @private
-
1
def controller_class
-
7
described_class
-
end
-
-
# Supports a simple DSL for specifying behavior of ApplicationController.
-
# Creates an anonymous subclass of ApplicationController and evals the
-
# `body` in that context. Also sets up implicit routes for this
-
# controller, that are separate from those defined in "config/routes.rb".
-
#
-
# @note Due to Ruby 1.8 scoping rules in anonymous subclasses, constants
-
# defined in `ApplicationController` must be fully qualified (e.g.
-
# `ApplicationController::AccessDenied`) in the block passed to the
-
# `controller` method. Any instance methods, filters, etc, that are
-
# defined in `ApplicationController`, however, are accessible from
-
# within the block.
-
#
-
# @example
-
# describe ApplicationController do
-
# controller do
-
# def index
-
# raise ApplicationController::AccessDenied
-
# end
-
# end
-
#
-
# describe "handling AccessDenied exceptions" do
-
# it "redirects to the /401.html page" do
-
# get :index
-
# response.should redirect_to("/401.html")
-
# end
-
# end
-
# end
-
#
-
# If you would like to spec a subclass of ApplicationController, call
-
# controller like so:
-
#
-
# controller(ApplicationControllerSubclass) do
-
# # ....
-
# end
-
1
def controller(base_class = nil, &body)
-
if RSpec.configuration.infer_base_class_for_anonymous_controllers?
-
base_class ||= controller_class
-
end
-
base_class ||= defined?(ApplicationController) ? ApplicationController : ActionController::Base
-
-
new_controller_class = Class.new(base_class) do
-
def self.name
-
root_controller = defined?(ApplicationController) ? ApplicationController : ActionController::Base
-
if superclass == root_controller || superclass.abstract?
-
"AnonymousController"
-
else
-
superclass.name
-
end
-
end
-
end
-
new_controller_class.class_exec(&body)
-
(class << self; self; end).__send__(:define_method, :controller_class) { new_controller_class }
-
-
before do
-
@orig_routes = routes
-
resource_name = if @controller.respond_to?(:controller_name)
-
@controller.controller_name.to_sym
-
else
-
:anonymous
-
end
-
resource_path = if @controller.respond_to?(:controller_path)
-
@controller.controller_path
-
else
-
resource_name.to_s
-
end
-
resource_module = resource_path.rpartition('/').first.presence
-
resource_as = 'anonymous_' + resource_path.tr('/', '_')
-
self.routes = ActionDispatch::Routing::RouteSet.new.tap do |r|
-
r.draw do
-
resources resource_name,
-
:as => resource_as,
-
:module => resource_module,
-
:path => resource_path
-
end
-
end
-
end
-
-
after do
-
self.routes = @orig_routes
-
@orig_routes = nil
-
end
-
end
-
-
# Specifies the routeset that will be used for the example group. This
-
# is most useful when testing Rails engines.
-
#
-
# @example
-
# describe MyEngine::PostsController do
-
# routes { MyEngine::Engine.routes }
-
#
-
# # ...
-
# end
-
1
def routes
-
before do
-
self.routes = yield
-
end
-
end
-
end
-
-
# @!attribute [r]
-
# Returns the controller object instance under test.
-
1
attr_reader :controller
-
-
# @!attribute [r]
-
# Returns the Rails routes used for the spec.
-
1
attr_reader :routes
-
-
# @private
-
#
-
# RSpec Rails uses this to make Rails routes easily available to specs.
-
1
def routes=(routes)
-
7
@routes = routes
-
7
assertion_instance.instance_variable_set(:@routes, routes)
-
end
-
-
# @private
-
1
module BypassRescue
-
1
def rescue_with_handler(exception)
-
raise exception
-
end
-
end
-
-
# Extends the controller with a module that overrides
-
# `rescue_with_handler` to raise the exception passed to it. Use this to
-
# specify that an action _should_ raise an exception given appropriate
-
# conditions.
-
#
-
# @example
-
# describe ProfilesController do
-
# it "raises a 403 when a non-admin user tries to view another user's profile" do
-
# profile = create_profile
-
# login_as profile.user
-
#
-
# expect do
-
# bypass_rescue
-
# get :show, :id => profile.id + 1
-
# end.to raise_error(/403 Forbidden/)
-
# end
-
# end
-
1
def bypass_rescue
-
controller.extend(BypassRescue)
-
end
-
-
# If method is a named_route, delegates to the RouteSet associated with
-
# this controller.
-
1
def method_missing(method, *args, &block)
-
if route_available?(method)
-
controller.send(method, *args, &block)
-
else
-
super
-
end
-
end
-
-
1
included do
-
1
subject { controller }
-
-
1
before do
-
7
self.routes = ::Rails.application.routes
-
end
-
-
1
around do |ex|
-
7
previous_allow_forgery_protection_value = ActionController::Base.allow_forgery_protection
-
7
begin
-
7
ActionController::Base.allow_forgery_protection = false
-
7
ex.call
-
ensure
-
7
ActionController::Base.allow_forgery_protection = previous_allow_forgery_protection_value
-
end
-
end
-
end
-
-
1
private
-
-
1
def route_available?(method)
-
(defined?(@routes) && route_defined?(routes, method)) ||
-
(defined?(@orig_routes) && route_defined?(@orig_routes, method))
-
end
-
-
1
def route_defined?(routes, method)
-
return false if routes.nil?
-
-
if routes.named_routes.respond_to?(:route_defined?)
-
routes.named_routes.route_defined?(method)
-
else
-
routes.named_routes.helpers.include?(method)
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# Container module for routing spec functionality.
-
1
module FeatureExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
-
# Default host to be used in Rails route helpers if none is specified.
-
1
DEFAULT_HOST = "www.example.com"
-
-
1
included do
-
app = ::Rails.application
-
if app.respond_to?(:routes)
-
include app.routes.url_helpers if app.routes.respond_to?(:url_helpers)
-
include app.routes.mounted_helpers if app.routes.respond_to?(:mounted_helpers)
-
-
if respond_to?(:default_url_options)
-
default_url_options[:host] ||= ::RSpec::Rails::FeatureExampleGroup::DEFAULT_HOST
-
end
-
end
-
end
-
-
# Shim to check for presence of Capybara. Will delegate if present, raise
-
# if not. We assume here that in most cases `visit` will be the first
-
# Capybara method called in a spec.
-
1
def visit(*)
-
if defined?(super)
-
super
-
else
-
raise "Capybara not loaded, please add it to your Gemfile:\n\ngem \"capybara\""
-
end
-
end
-
end
-
end
-
end
-
-
1
unless RSpec.respond_to?(:feature)
-
1
opts = {
-
:capybara_feature => true,
-
:type => :feature,
-
:skip => <<-EOT.squish
-
Feature specs require the Capybara (http://github.com/jnicklas/capybara)
-
gem, version 2.2.0 or later. We recommend version 2.4.0 or later to avoid
-
some deprecation warnings and have support for
-
`config.expose_dsl_globally = false`.
-
EOT
-
}
-
-
# Capybara's monkey patching causes us to have to jump through some hoops
-
1
top_level = self
-
1
main_feature = nil
-
1
if defined?(Capybara) && ::Capybara::VERSION.to_f < 2.4
-
# Capybara 2.2 and 2.3 do not use `alias_example_xyz`
-
opts[:skip] = <<-EOT.squish
-
Capybara < 2.4.0 does not support RSpec's namespace or
-
`config.expose_dsl_globally = false`. Upgrade to Capybara >= 2.4.0.
-
EOT
-
main_feature = top_level.method(:feature) if top_level.respond_to?(:feature)
-
end
-
-
1
RSpec.configure do |c|
-
1
main_feature = nil unless c.expose_dsl_globally?
-
1
c.alias_example_group_to :feature, opts
-
1
c.alias_example_to :scenario
-
1
c.alias_example_to :xscenario
-
end
-
-
# Due to load order issues and `config.expose_dsl_globally?` defaulting to
-
# `true` we need to put Capybara's monkey patch method back. Otherwise,
-
# app upgrades have a high likelyhood of having all feature specs skipped.
-
1
top_level.define_singleton_method(:feature, &main_feature) if main_feature
-
end
-
1
require 'rspec/rails/view_assigns'
-
-
1
module RSpec
-
1
module Rails
-
# Container module for helper specs.
-
1
module HelperExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
1
include ActionView::TestCase::Behavior
-
1
include RSpec::Rails::ViewAssigns
-
-
# @private
-
1
module ClassMethods
-
1
def determine_default_helper_class(_ignore)
-
described_class
-
end
-
end
-
-
# Returns an instance of ActionView::Base with the helper being specified
-
# mixed in, along with any of the built-in rails helpers.
-
1
def helper
-
_view.tap do |v|
-
v.extend(ApplicationHelper) if defined?(ApplicationHelper)
-
v.assign(view_assigns)
-
end
-
end
-
-
1
private
-
-
1
def _controller_path(example)
-
example.example_group.described_class.to_s.sub(/Helper/, '').underscore
-
end
-
-
1
included do
-
before do |example|
-
controller.controller_path = _controller_path(example)
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# Container module for job spec functionality. It is only available if
-
# ActiveJob has been loaded before it.
-
1
module JobExampleGroup
-
# This blank module is only necessary for YARD processing. It doesn't
-
# handle the conditional `defined?` check below very well.
-
end
-
end
-
end
-
-
1
if defined?(ActiveJob)
-
1
module RSpec
-
1
module Rails
-
# Container module for job spec functionality.
-
1
module JobExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# Container module for mailer spec functionality. It is only available if
-
# ActionMailer has been loaded before it.
-
1
module MailerExampleGroup
-
# This blank module is only necessary for YARD processing. It doesn't
-
# handle the conditional `defined?` check below very well.
-
end
-
end
-
end
-
-
1
if defined?(ActionMailer)
-
1
module RSpec
-
1
module Rails
-
# Container module for mailer spec functionality.
-
1
module MailerExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
1
include ActionMailer::TestCase::Behavior
-
-
1
included do
-
include ::Rails.application.routes.url_helpers
-
options = ::Rails.configuration.action_mailer.default_url_options
-
options.each { |key, value| default_url_options[key] = value } if options
-
end
-
-
# Class-level DSL for mailer specs.
-
1
module ClassMethods
-
# Alias for `described_class`.
-
1
def mailer_class
-
described_class
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# Container class for model spec functionality. Does not provide anything
-
# special over the common RailsExampleGroup currently.
-
1
module ModelExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
end
-
end
-
end
-
# Temporary workaround to resolve circular dependency between rspec-rails' spec
-
# suite and ammeter.
-
1
require 'rspec/rails/matchers'
-
-
1
module RSpec
-
1
module Rails
-
# Common rails example functionality.
-
1
module RailsExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::SetupAndTeardownAdapter
-
1
include RSpec::Rails::MinitestLifecycleAdapter if ::Rails::VERSION::STRING >= '4'
-
1
include RSpec::Rails::MinitestAssertionAdapter
-
1
include RSpec::Rails::Matchers
-
1
include RSpec::Rails::FixtureSupport
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# Container class for request spec functionality.
-
1
module RequestExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
1
include ActionDispatch::Integration::Runner
-
1
include ActionDispatch::Assertions
-
1
include RSpec::Rails::Matchers::RedirectTo
-
1
include RSpec::Rails::Matchers::RenderTemplate
-
1
include ActionController::TemplateAssertions
-
-
# Delegates to `Rails.application`.
-
1
def app
-
::Rails.application
-
end
-
-
1
included do
-
before do
-
@routes = ::Rails.application.routes
-
end
-
end
-
end
-
end
-
end
-
1
require "action_dispatch/testing/assertions/routing"
-
-
1
module RSpec
-
1
module Rails
-
# @private
-
1
RoutingAssertionDelegator = RSpec::Rails::AssertionDelegator.new(
-
ActionDispatch::Assertions::RoutingAssertions
-
)
-
-
# Container module for routing spec functionality.
-
1
module RoutingExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
1
include RSpec::Rails::Matchers::RoutingMatchers
-
1
include RSpec::Rails::Matchers::RoutingMatchers::RouteHelpers
-
1
include RSpec::Rails::RoutingAssertionDelegator
-
-
# Class-level DSL for route specs.
-
1
module ClassMethods
-
# Specifies the routeset that will be used for the example group. This
-
# is most useful when testing Rails engines.
-
#
-
# @example
-
# describe MyEngine::PostsController do
-
# routes { MyEngine::Engine.routes }
-
#
-
# it "routes posts#index" do
-
# expect(:get => "/posts").to
-
# route_to(:controller => "my_engine/posts", :action => "index")
-
# end
-
# end
-
1
def routes
-
before do
-
self.routes = yield
-
end
-
end
-
end
-
-
1
included do
-
before do
-
self.routes = ::Rails.application.routes
-
end
-
end
-
-
# @!attribute [r]
-
# @private
-
1
attr_reader :routes
-
-
# @private
-
1
def routes=(routes)
-
@routes = routes
-
assertion_instance.instance_variable_set(:@routes, routes)
-
end
-
-
1
private
-
-
1
def method_missing(m, *args, &block)
-
routes.url_helpers.respond_to?(m) ? routes.url_helpers.send(m, *args) : super
-
end
-
end
-
end
-
end
-
1
require 'rspec/rails/view_assigns'
-
-
1
module RSpec
-
1
module Rails
-
# Container class for view spec functionality.
-
1
module ViewExampleGroup
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::RailsExampleGroup
-
1
include ActionView::TestCase::Behavior
-
1
include RSpec::Rails::ViewAssigns
-
1
include RSpec::Rails::Matchers::RenderTemplate
-
-
# @private
-
1
module ClassMethods
-
1
def _default_helper
-
base = metadata[:description].split('/')[0..-2].join('/')
-
(base.camelize + 'Helper').constantize unless base.to_s.empty?
-
rescue NameError
-
nil
-
end
-
-
1
def _default_helpers
-
helpers = [_default_helper].compact
-
helpers << ApplicationHelper if Object.const_defined?('ApplicationHelper')
-
helpers
-
end
-
end
-
-
# DSL exposed to view specs.
-
1
module ExampleMethods
-
# @overload render
-
# @overload render({:partial => path_to_file})
-
# @overload render({:partial => path_to_file}, {... locals ...})
-
# @overload render({:partial => path_to_file}, {... locals ...}) do ... end
-
#
-
# Delegates to ActionView::Base#render, so see documentation on that
-
# for more info.
-
#
-
# The only addition is that you can call render with no arguments, and
-
# RSpec will pass the top level description to render:
-
#
-
# describe "widgets/new.html.erb" do
-
# it "shows all the widgets" do
-
# render # => view.render(:file => "widgets/new.html.erb")
-
# # ...
-
# end
-
# end
-
1
def render(options = {}, local_assigns = {}, &block)
-
options = _default_render_options if Hash === options && options.empty?
-
super(options, local_assigns, &block)
-
end
-
-
# The instance of `ActionView::Base` that is used to render the template.
-
# Use this to stub methods _before_ calling `render`.
-
#
-
# describe "widgets/new.html.erb" do
-
# it "shows all the widgets" do
-
# view.stub(:foo) { "foo" }
-
# render
-
# # ...
-
# end
-
# end
-
1
def view
-
_view
-
end
-
-
# Simulates the presence of a template on the file system by adding a
-
# Rails' FixtureResolver to the front of the view_paths list. Designed to
-
# help isolate view examples from partials rendered by the view template
-
# that is the subject of the example.
-
#
-
# stub_template("widgets/_widget.html.erb" => "This content.")
-
1
def stub_template(hash)
-
view.view_paths.unshift(ActionView::FixtureResolver.new(hash))
-
end
-
-
# Provides access to the params hash that will be available within the
-
# view.
-
#
-
# params[:foo] = 'bar'
-
1
def params
-
controller.params
-
end
-
-
# @deprecated Use `view` instead.
-
1
def template
-
RSpec.deprecate("template", :replacement => "view")
-
view
-
end
-
-
# @deprecated Use `rendered` instead.
-
1
def response
-
# `assert_template` expects `response` to implement a #body method
-
# like an `ActionDispatch::Response` does to force the view to
-
# render. For backwards compatibility, we use #response as an alias
-
# for #rendered, but it needs to implement #body to avoid
-
# `assert_template` raising a `NoMethodError`.
-
unless rendered.respond_to?(:body)
-
def rendered.body
-
self
-
end
-
end
-
-
rendered
-
end
-
-
1
private
-
-
1
def _default_render_options
-
if ::Rails::VERSION::STRING >= '3.2'
-
# pluck the handler, format, and locale out of, eg, posts/index.de.html.haml
-
template, *components = _default_file_to_render.split('.')
-
handler, format, locale = *components.reverse
-
-
render_options = { :template => template }
-
render_options[:handlers] = [handler] if handler
-
render_options[:formats] = [format] if format
-
render_options[:locales] = [locale] if locale
-
-
render_options
-
else
-
{ :template => _default_file_to_render }
-
end
-
end
-
-
1
def _path_parts
-
_default_file_to_render.split("/")
-
end
-
-
1
def _controller_path
-
_path_parts[0..-2].join("/")
-
end
-
-
1
def _inferred_action
-
_path_parts.last.split(".").first
-
end
-
-
1
def _include_controller_helpers
-
helpers = controller._helpers
-
view.singleton_class.class_exec do
-
include helpers unless included_modules.include?(helpers)
-
end
-
end
-
end
-
-
1
included do
-
include ExampleMethods
-
-
helper(*_default_helpers)
-
-
before do
-
_include_controller_helpers
-
if view.lookup_context.respond_to?(:prefixes)
-
# rails 3.1
-
view.lookup_context.prefixes << _controller_path
-
end
-
-
# fixes bug with differing formats
-
view.lookup_context.view_paths.each(&:clear_cache)
-
-
controller.controller_path = _controller_path
-
controller.request.path_parameters[:controller] = _controller_path
-
controller.request.path_parameters[:action] = _inferred_action unless _inferred_action =~ /^_/
-
end
-
-
let(:_default_file_to_render) do |example|
-
example.example_group.top_level_description
-
end
-
end
-
end
-
end
-
end
-
1
require 'rspec/rails/extensions/active_record/proxy'
-
1
RSpec.configure do |rspec|
-
# Delay this in order to give users a chance to configure `expect_with`...
-
1
rspec.before(:suite) do
-
1
if defined?(RSpec::Matchers) && RSpec::Matchers.configuration.syntax.include?(:should) && defined?(ActiveRecord::Associations)
-
# In Rails 3.0, it was AssociationProxy.
-
# In 3.1+, it's CollectionProxy.
-
1
const_name = [:CollectionProxy, :AssociationProxy].find do |const|
-
1
ActiveRecord::Associations.const_defined?(const)
-
end
-
-
1
proxy_class = ActiveRecord::Associations.const_get(const_name)
-
-
1
RSpec::Matchers.configuration.add_should_and_should_not_to proxy_class
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# @private
-
1
module FixtureSupport
-
1
if defined?(ActiveRecord::TestFixtures)
-
1
extend ActiveSupport::Concern
-
1
include RSpec::Rails::SetupAndTeardownAdapter
-
1
include RSpec::Rails::MinitestLifecycleAdapter if ::ActiveRecord::VERSION::STRING > '4'
-
1
include RSpec::Rails::MinitestAssertionAdapter
-
1
include ActiveRecord::TestFixtures
-
-
1
included do
-
# TODO: (DC 2011-06-25) this is necessary because fixture_file_upload
-
# accesses fixture_path directly on ActiveSupport::TestCase. This is
-
# fixed in rails by https://github.com/rails/rails/pull/1861, which
-
# should be part of the 3.1 release, at which point we can include
-
# these lines for rails < 3.1.
-
1
ActiveSupport::TestCase.class_exec do
-
1
include ActiveRecord::TestFixtures
-
1
self.fixture_path = RSpec.configuration.fixture_path
-
end
-
# /TODO
-
-
1
self.fixture_path = RSpec.configuration.fixture_path
-
1
self.use_transactional_fixtures = RSpec.configuration.use_transactional_fixtures
-
1
self.use_instantiated_fixtures = RSpec.configuration.use_instantiated_fixtures
-
1
fixtures RSpec.configuration.global_fixtures if RSpec.configuration.global_fixtures
-
end
-
end
-
end
-
end
-
end
-
1
require 'rspec/core/warnings'
-
1
require 'rspec/expectations'
-
-
1
module RSpec
-
1
module Rails
-
# Container module for Rails specific matchers.
-
1
module Matchers
-
end
-
end
-
end
-
-
1
require 'rspec/rails/matchers/have_rendered'
-
1
require 'rspec/rails/matchers/redirect_to'
-
1
require 'rspec/rails/matchers/routing_matchers'
-
1
require 'rspec/rails/matchers/be_new_record'
-
1
require 'rspec/rails/matchers/be_a_new'
-
1
require 'rspec/rails/matchers/relation_match_array'
-
1
require 'rspec/rails/matchers/be_valid'
-
1
require 'rspec/rails/matchers/have_http_status'
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# @api private
-
#
-
# Matcher class for `be_a_new`. Should not be instantiated directly.
-
#
-
# @see RSpec::Rails::Matchers#be_a_new
-
1
class BeANew < RSpec::Matchers::BuiltIn::BaseMatcher
-
# @private
-
1
def initialize(expected)
-
@expected = expected
-
end
-
-
# @private
-
1
def matches?(actual)
-
@actual = actual
-
actual.is_a?(expected) && actual.new_record? && attributes_match?(actual)
-
end
-
-
# @api public
-
# @see RSpec::Rails::Matchers#be_a_new
-
1
def with(expected_attributes)
-
attributes.merge!(expected_attributes)
-
self
-
end
-
-
# @private
-
1
def failure_message
-
[].tap do |message|
-
unless actual.is_a?(expected) && actual.new_record?
-
message << "expected #{actual.inspect} to be a new #{expected.inspect}"
-
end
-
unless attributes_match?(actual)
-
if unmatched_attributes.size > 1
-
message << "attributes #{unmatched_attributes.inspect} were not set on #{actual.inspect}"
-
else
-
message << "attribute #{unmatched_attributes.inspect} was not set on #{actual.inspect}"
-
end
-
end
-
end.join(' and ')
-
end
-
-
1
private
-
-
1
def attributes
-
@attributes ||= {}
-
end
-
-
1
def attributes_match?(actual)
-
attributes.stringify_keys.all? do |key, value|
-
actual.attributes[key].eql?(value)
-
end
-
end
-
-
1
def unmatched_attributes
-
attributes.stringify_keys.reject do |key, value|
-
actual.attributes[key].eql?(value)
-
end
-
end
-
end
-
-
# Passes if actual is an instance of `model_class` and returns `false` for
-
# `persisted?`. Typically used to specify instance variables assigned to
-
# views by controller actions
-
#
-
# Use the `with` method to specify the specific attributes to match on the
-
# new record.
-
#
-
# @example
-
# get :new
-
# assigns(:thing).should be_a_new(Thing)
-
#
-
# post :create, :thing => { :name => "Illegal Value" }
-
# assigns(:thing).should be_a_new(Thing).with(:name => nil)
-
1
def be_a_new(model_class)
-
BeANew.new(model_class)
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# @private
-
1
class BeANewRecord < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
def matches?(actual)
-
!actual.persisted?
-
end
-
-
1
def failure_message
-
"expected #{actual.inspect} to be a new record, but was persisted"
-
end
-
-
1
def failure_message_when_negated
-
"expected #{actual.inspect} to be persisted, but was a new record"
-
end
-
end
-
-
# Passes if actual returns `false` for `persisted?`.
-
#
-
# @example
-
# get :new
-
# expect(assigns(:thing)).to be_new_record
-
1
def be_new_record
-
BeANewRecord.new
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# @private
-
1
class BeValid < RSpec::Matchers::BuiltIn::Be
-
1
def initialize(*args)
-
@args = args
-
end
-
-
1
def matches?(actual)
-
@actual = actual
-
actual.valid?(*@args)
-
end
-
-
1
def failure_message
-
message = "expected #{actual.inspect} to be valid"
-
-
if actual.respond_to?(:errors)
-
errors = if actual.errors.respond_to?(:full_messages)
-
actual.errors.full_messages
-
else
-
actual.errors
-
end
-
-
message << ", but got errors: #{errors.map(&:to_s).join(', ')}"
-
end
-
-
message
-
end
-
-
1
def failure_message_when_negated
-
"expected #{actual.inspect} not to be valid"
-
end
-
end
-
-
# Passes if the given model instance's `valid?` method is true, meaning
-
# all of the `ActiveModel::Validations` passed and no errors exist. If a
-
# message is not given, a default message is shown listing each error.
-
#
-
# @example
-
# thing = Thing.new
-
# expect(thing).to be_valid
-
1
def be_valid(*args)
-
BeValid.new(*args)
-
end
-
end
-
end
-
end
-
# The following code inspired and modified from Rails' `assert_response`:
-
#
-
# https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/assertions/response.rb#L22-L38
-
#
-
# Thank you to all the Rails devs who did the heavy lifting on this!
-
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# Namespace for various implementations of `have_http_status`.
-
#
-
# @api private
-
1
module HaveHttpStatus
-
# Instantiates an instance of the proper matcher based on the provided
-
# `target`.
-
#
-
# @param target [Object] expected http status or code
-
# @return response matcher instance
-
1
def self.matcher_for_status(target)
-
if GenericStatus.valid_statuses.include?(target)
-
GenericStatus.new(target)
-
elsif Symbol === target
-
SymbolicStatus.new(target)
-
else
-
NumericCode.new(target)
-
end
-
end
-
-
# @api private
-
# Conversion function to coerce the provided object into an
-
# `ActionDispatch::TestResponse`.
-
#
-
# @param obj [Object] object to convert to a response
-
# @return [ActionDispatch::TestResponse]
-
1
def as_test_response(obj)
-
if ::ActionDispatch::Response === obj
-
::ActionDispatch::TestResponse.from_response(obj)
-
elsif ::ActionDispatch::TestResponse === obj
-
obj
-
elsif obj.respond_to?(:status_code) && obj.respond_to?(:response_headers)
-
# Acts As Capybara Session
-
# Hack to support `Capybara::Session` without having to load
-
# Capybara or catch `NameError`s for the undefined constants
-
::ActionDispatch::TestResponse.new.tap do |resp|
-
resp.status = obj.status_code
-
resp.headers = obj.response_headers
-
resp.body = obj.body
-
end
-
else
-
raise TypeError, "Invalid response type: #{obj}"
-
end
-
end
-
1
module_function :as_test_response
-
-
# @return [String, nil] a formatted failure message if
-
# `@invalid_response` is present, `nil` otherwise
-
1
def invalid_response_type_message
-
return unless @invalid_response
-
"expected a response object, but an instance of " \
-
"#{@invalid_response.class} was received"
-
end
-
-
# @api private
-
# Provides an implementation for `have_http_status` matching against
-
# numeric http status codes.
-
#
-
# Not intended to be instantiated directly.
-
#
-
# @example
-
# expect(response).to have_http_status(404)
-
#
-
# @see RSpec::Rails::Matchers.have_http_status
-
1
class NumericCode < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
include HaveHttpStatus
-
-
1
def initialize(code)
-
@expected = code.to_i
-
@actual = nil
-
@invalid_response = nil
-
end
-
-
# @param [Object] response object providing an http code to match
-
# @return [Boolean] `true` if the numeric code matched the `response` code
-
1
def matches?(response)
-
test_response = as_test_response(response)
-
@actual = test_response.response_code
-
expected == @actual
-
rescue TypeError => _ignored
-
@invalid_response = response
-
false
-
end
-
-
# @return [String]
-
1
def description
-
"respond with numeric status code #{expected}"
-
end
-
-
# @return [String] explaining why the match failed
-
1
def failure_message
-
invalid_response_type_message ||
-
"expected the response to have status code #{expected.inspect}" \
-
" but it was #{actual.inspect}"
-
end
-
-
# @return [String] explaining why the match failed
-
1
def failure_message_when_negated
-
invalid_response_type_message ||
-
"expected the response not to have status code " \
-
"#{expected.inspect} but it did"
-
end
-
end
-
-
# @api private
-
# Provides an implementation for `have_http_status` matching against
-
# Rack symbol http status codes.
-
#
-
# Not intended to be instantiated directly.
-
#
-
# @example
-
# expect(response).to have_http_status(:created)
-
#
-
# @see RSpec::Rails::Matchers.have_http_status
-
# @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
-
1
class SymbolicStatus < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
include HaveHttpStatus
-
-
1
def initialize(status)
-
@expected_status = status
-
@actual = nil
-
@invalid_response = nil
-
set_expected_code!
-
end
-
-
# @param [Object] response object providing an http code to match
-
# @return [Boolean] `true` if Rack's associated numeric HTTP code matched
-
# the `response` code
-
1
def matches?(response)
-
test_response = as_test_response(response)
-
@actual = test_response.response_code
-
expected == @actual
-
rescue TypeError => _ignored
-
@invalid_response = response
-
false
-
end
-
-
# @return [String]
-
1
def description
-
"respond with status code #{pp_expected}"
-
end
-
-
# @return [String] explaining why the match failed
-
1
def failure_message
-
invalid_response_type_message ||
-
"expected the response to have status code #{pp_expected} but it" \
-
" was #{pp_actual}"
-
end
-
-
# @return [String] explaining why the match failed
-
1
def failure_message_when_negated
-
invalid_response_type_message ||
-
"expected the response not to have status code #{pp_expected} " \
-
"but it did"
-
end
-
-
# The initialized expected status symbol
-
1
attr_reader :expected_status
-
1
private :expected_status
-
-
1
private
-
-
# @return [Symbol] representing the actual http numeric code
-
1
def actual_status
-
return unless actual
-
@actual_status ||= compute_status_from(actual)
-
end
-
-
# Reverse lookup of the Rack status code symbol based on the numeric
-
# http code
-
#
-
# @param code [Fixnum] http status code to look up
-
# @return [Symbol] representing the http numeric code
-
1
def compute_status_from(code)
-
status, _ = Rack::Utils::SYMBOL_TO_STATUS_CODE.find do |_, c|
-
c == code
-
end
-
status
-
end
-
-
# @return [String] pretty format the actual response status
-
1
def pp_actual
-
pp_status(actual_status, actual)
-
end
-
-
# @return [String] pretty format the expected status and associated code
-
1
def pp_expected
-
pp_status(expected_status, expected)
-
end
-
-
# @return [String] pretty format the actual response status
-
1
def pp_status(status, code)
-
if status
-
"#{status.inspect} (#{code})"
-
else
-
code.to_s
-
end
-
end
-
-
# Sets `expected` to the numeric http code based on the Rack
-
# `expected_status` status
-
#
-
# @see Rack::Utils::SYMBOL_TO_STATUS_CODE
-
# @raise [ArgumentError] if an associated code could not be found
-
1
def set_expected_code!
-
@expected ||=
-
Rack::Utils::SYMBOL_TO_STATUS_CODE.fetch(expected_status) do
-
raise ArgumentError,
-
"Invalid HTTP status: #{expected_status.inspect}"
-
end
-
end
-
end
-
-
# @api private
-
# Provides an implementation for `have_http_status` matching against
-
# `ActionDispatch::TestResponse` http status category queries.
-
#
-
# Not intended to be instantiated directly.
-
#
-
# @example
-
# expect(response).to have_http_status(:success)
-
# expect(response).to have_http_status(:error)
-
# expect(response).to have_http_status(:missing)
-
# expect(response).to have_http_status(:redirect)
-
#
-
# @see RSpec::Rails::Matchers.have_http_status
-
# @see ActionDispatch::TestResponse
-
1
class GenericStatus < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
include HaveHttpStatus
-
-
# @return [Array<Symbol>] of status codes which represent a HTTP status
-
# code "group"
-
# @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
-
1
def self.valid_statuses
-
[:error, :success, :missing, :redirect]
-
end
-
-
1
def initialize(type)
-
unless self.class.valid_statuses.include?(type)
-
raise ArgumentError, "Invalid generic HTTP status: #{type.inspect}"
-
end
-
@expected = type
-
@actual = nil
-
@invalid_response = nil
-
end
-
-
# @return [Boolean] `true` if Rack's associated numeric HTTP code matched
-
# the `response` code
-
1
def matches?(response)
-
test_response = as_test_response(response)
-
@actual = test_response.response_code
-
test_response.send("#{expected}?")
-
rescue TypeError => _ignored
-
@invalid_response = response
-
false
-
end
-
-
# @return [String]
-
1
def description
-
"respond with #{type_message}"
-
end
-
-
# @return [String] explaining why the match failed
-
1
def failure_message
-
invalid_response_type_message ||
-
"expected the response to have #{type_message} but it was #{actual}"
-
end
-
-
# @return [String] explaining why the match failed
-
1
def failure_message_when_negated
-
invalid_response_type_message ||
-
"expected the response not to have #{type_message} but it was #{actual}"
-
end
-
-
1
private
-
-
# @return [String] formating the expected status and associated code(s)
-
1
def type_message
-
@type_message ||= (expected == :error ? "an error" : "a #{expected}") +
-
" status code (#{type_codes})"
-
end
-
-
# @return [String] formatting the associated code(s) for the various
-
# status code "groups"
-
# @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
-
# @see https://github.com/rack/rack/blob/master/lib/rack/response.rb `Rack::Response`
-
1
def type_codes
-
# At the time of this commit the most recent version of
-
# `ActionDispatch::TestResponse` defines the following aliases:
-
#
-
# alias_method :success?, :successful?
-
# alias_method :missing?, :not_found?
-
# alias_method :redirect?, :redirection?
-
# alias_method :error?, :server_error?
-
#
-
# It's parent `ActionDispatch::Response` includes
-
# `Rack::Response::Helpers` which defines the aliased methods as:
-
#
-
# def successful?; status >= 200 && status < 300; end
-
# def redirection?; status >= 300 && status < 400; end
-
# def server_error?; status >= 500 && status < 600; end
-
# def not_found?; status == 404; end
-
#
-
# @see https://github.com/rails/rails/blob/ca200378/actionpack/lib/action_dispatch/testing/test_response.rb#L17-L27
-
# @see https://github.com/rails/rails/blob/ca200378/actionpack/lib/action_dispatch/http/response.rb#L74
-
# @see https://github.com/rack/rack/blob/ce4a3959/lib/rack/response.rb#L119-L122
-
@type_codes ||= case expected
-
when :error
-
"5xx"
-
when :success
-
"2xx"
-
when :missing
-
"404"
-
when :redirect
-
"3xx"
-
end
-
end
-
end
-
end
-
-
# @api public
-
# Passes if `response` has a matching HTTP status code.
-
#
-
# The following symbolic status codes are allowed:
-
#
-
# - `Rack::Utils::SYMBOL_TO_STATUS_CODE`
-
# - One of the defined `ActionDispatch::TestResponse` aliases:
-
# - `:error`
-
# - `:missing`
-
# - `:redirect`
-
# - `:success`
-
#
-
# @example Accepts numeric and symbol statuses
-
# expect(response).to have_http_status(404)
-
# expect(response).to have_http_status(:created)
-
# expect(response).to have_http_status(:success)
-
# expect(response).to have_http_status(:error)
-
# expect(response).to have_http_status(:missing)
-
# expect(response).to have_http_status(:redirect)
-
#
-
# @example Works with standard `response` objects and Capybara's `page`
-
# expect(response).to have_http_status(404)
-
# expect(page).to have_http_status(:created)
-
#
-
# @see https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/testing/test_response.rb `ActionDispatch::TestResponse`
-
# @see https://github.com/rack/rack/blob/master/lib/rack/utils.rb `Rack::Utils::SYMBOL_TO_STATUS_CODE`
-
1
def have_http_status(target)
-
raise ArgumentError, "Invalid HTTP status: nil" unless target
-
HaveHttpStatus.matcher_for_status(target)
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# Matcher for template rendering.
-
1
module RenderTemplate
-
# @private
-
1
class RenderTemplateMatcher < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
def initialize(scope, expected, message = nil)
-
@expected = Symbol === expected ? expected.to_s : expected
-
@message = message
-
@scope = scope
-
end
-
-
# @api private
-
1
def matches?(*)
-
match_unless_raises ActiveSupport::TestCase::Assertion do
-
@scope.assert_template expected, @message
-
end
-
end
-
-
# @api private
-
1
def failure_message
-
rescued_exception.message
-
end
-
-
# @api private
-
1
def failure_message_when_negated
-
"expected not to render #{expected.inspect}, but did"
-
end
-
end
-
-
# Delegates to `assert_template`.
-
#
-
# @example
-
# expect(response).to have_rendered("new")
-
1
def have_rendered(options, message = nil)
-
RenderTemplateMatcher.new(self, options, message)
-
end
-
-
1
alias_method :render_template, :have_rendered
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# Matcher for redirects.
-
1
module RedirectTo
-
# @private
-
1
class RedirectTo < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
def initialize(scope, expected)
-
@expected = expected
-
@scope = scope
-
end
-
-
1
def matches?(_)
-
match_unless_raises ActiveSupport::TestCase::Assertion do
-
@scope.assert_redirected_to(@expected)
-
end
-
end
-
-
1
def failure_message
-
rescued_exception.message
-
end
-
-
1
def failure_message_when_negated
-
"expected not to redirect to #{@expected.inspect}, but did"
-
end
-
end
-
-
# Delegates to `assert_redirected_to`.
-
#
-
# @example
-
# expect(response).to redirect_to(:action => "new")
-
1
def redirect_to(target)
-
RedirectTo.new(self, target)
-
end
-
end
-
end
-
end
-
end
-
1
if defined?(ActiveRecord::Relation)
-
1
RSpec::Matchers::BuiltIn::OperatorMatcher.register(ActiveRecord::Relation, '=~', RSpec::Matchers::BuiltIn::ContainExactly)
-
end
-
1
module RSpec
-
1
module Rails
-
1
module Matchers
-
# Matchers to help with specs for routing code.
-
1
module RoutingMatchers
-
1
extend RSpec::Matchers::DSL
-
-
# @private
-
1
class RouteToMatcher < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
def initialize(scope, *expected)
-
@scope = scope
-
@expected = expected[1] || {}
-
if Hash === expected[0]
-
@expected.merge!(expected[0])
-
else
-
controller, action = expected[0].split('#')
-
@expected.merge!(:controller => controller, :action => action)
-
end
-
end
-
-
1
def matches?(verb_to_path_map)
-
@actual = @verb_to_path_map = verb_to_path_map
-
# assert_recognizes does not consider ActionController::RoutingError an
-
# assertion failure, so we have to capture that and Assertion here.
-
match_unless_raises ActiveSupport::TestCase::Assertion, ActionController::RoutingError do
-
path, query = *verb_to_path_map.values.first.split('?')
-
@scope.assert_recognizes(
-
@expected,
-
{ :method => verb_to_path_map.keys.first, :path => path },
-
Rack::Utils.parse_nested_query(query)
-
)
-
end
-
end
-
-
1
def failure_message
-
rescued_exception.message
-
end
-
-
1
def failure_message_when_negated
-
"expected #{@actual.inspect} not to route to #{@expected.inspect}"
-
end
-
-
1
def description
-
"route #{@actual.inspect} to #{@expected.inspect}"
-
end
-
end
-
-
# Delegates to `assert_recognizes`. Supports short-hand controller/action
-
# declarations (e.g. `"controller#action"`).
-
#
-
# @example
-
#
-
# expect(:get => "/things/special").to route_to(
-
# :controller => "things",
-
# :action => "special"
-
# )
-
#
-
# expect(:get => "/things/special").to route_to("things#special")
-
#
-
# @see http://api.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html#method-i-assert_recognizes
-
1
def route_to(*expected)
-
RouteToMatcher.new(self, *expected)
-
end
-
-
# @private
-
1
class BeRoutableMatcher < RSpec::Matchers::BuiltIn::BaseMatcher
-
1
def initialize(scope)
-
@scope = scope
-
end
-
-
1
def matches?(path)
-
@actual = path
-
match_unless_raises ActionController::RoutingError do
-
@routing_options = @scope.routes.recognize_path(
-
path.values.first, :method => path.keys.first
-
)
-
end
-
end
-
-
1
def failure_message
-
"expected #{@actual.inspect} to be routable"
-
end
-
-
1
def failure_message_when_negated
-
"expected #{@actual.inspect} not to be routable, but it routes to #{@routing_options.inspect}"
-
end
-
-
1
def description
-
"be routable"
-
end
-
end
-
-
# Passes if the route expression is recognized by the Rails router based on
-
# the declarations in `config/routes.rb`. Delegates to
-
# `RouteSet#recognize_path`.
-
#
-
# @example You can use route helpers provided by rspec-rails.
-
# expect(:get => "/a/path").to be_routable
-
# expect(:post => "/another/path").to be_routable
-
# expect(:put => "/yet/another/path").to be_routable
-
1
def be_routable
-
BeRoutableMatcher.new(self)
-
end
-
-
# Helpers for matching different route types.
-
1
module RouteHelpers
-
# @!method get
-
# @!method post
-
# @!method put
-
# @!method patch
-
# @!method delete
-
# @!method options
-
# @!method head
-
#
-
# Shorthand method for matching this type of route.
-
1
%w[get post put patch delete options head].each do |method|
-
7
define_method method do |path|
-
{ method.to_sym => path }
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
begin
-
1
require 'capybara/rspec'
-
rescue LoadError
-
end
-
-
1
begin
-
1
require 'capybara/rails'
-
rescue LoadError
-
end
-
-
1
if defined?(Capybara)
-
require 'rspec/support/version_checker'
-
RSpec::Support::VersionChecker.new('capybara', Capybara::VERSION, '2.2.0').check_version!
-
-
RSpec.configure do |c|
-
if defined?(Capybara::DSL)
-
c.include Capybara::DSL, :type => :feature
-
end
-
-
if defined?(Capybara::RSpecMatchers)
-
c.include Capybara::RSpecMatchers, :type => :view
-
c.include Capybara::RSpecMatchers, :type => :helper
-
c.include Capybara::RSpecMatchers, :type => :mailer
-
c.include Capybara::RSpecMatchers, :type => :controller
-
c.include Capybara::RSpecMatchers, :type => :feature
-
end
-
-
unless defined?(Capybara::RSpecMatchers) || defined?(Capybara::DSL)
-
c.include Capybara, :type => :request
-
c.include Capybara, :type => :controller
-
end
-
end
-
end
-
1
module RSpec
-
1
module Rails
-
# Helpers for making instance variables available to views.
-
1
module ViewAssigns
-
# Assigns a value to an instance variable in the scope of the
-
# view being rendered.
-
#
-
# @example
-
#
-
# assign(:widget, stub_model(Widget))
-
1
def assign(key, value)
-
_encapsulated_assigns[key] = value
-
end
-
-
# Compat-shim for AbstractController::Rendering#view_assigns
-
#
-
# _assigns was deprecated in favor of view_assigns after
-
# Rails-3.0.0 was released. Since we are not able to predict when
-
# the _assigns/view_assigns patch will be released (I thought it
-
# would have been in 3.0.1, but 3.0.1 bypassed this change for a
-
# security fix), this bit ensures that we do the right thing without
-
# knowing anything about the Rails version we are dealing with.
-
#
-
# Once that change _is_ released, this can be changed to something
-
# that checks for the Rails version when the module is being
-
# interpreted, as it was before commit dd0095.
-
1
def view_assigns
-
super.merge(_encapsulated_assigns)
-
rescue
-
_assigns
-
end
-
-
# @private
-
1
def _assigns
-
super.merge(_encapsulated_assigns)
-
end
-
-
1
private
-
-
1
def _encapsulated_assigns
-
@_encapsulated_assigns ||= {}
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# Provides a means to fuzzy-match between two arbitrary objects.
-
# Understands array/hash nesting. Uses `===` or `==` to
-
# perform the matching.
-
1
module FuzzyMatcher
-
# @api private
-
1
def self.values_match?(expected, actual)
-
if Hash === actual
-
return hashes_match?(expected, actual) if Hash === expected
-
elsif Array === expected && Enumerable === actual && !(Struct === actual)
-
return arrays_match?(expected, actual.to_a)
-
end
-
-
return true if expected == actual
-
-
begin
-
expected === actual
-
rescue ArgumentError
-
# Some objects, like 0-arg lambdas on 1.9+, raise
-
# ArgumentError for `expected === actual`.
-
false
-
end
-
end
-
-
# @private
-
1
def self.arrays_match?(expected_list, actual_list)
-
return false if expected_list.size != actual_list.size
-
-
expected_list.zip(actual_list).all? do |expected, actual|
-
values_match?(expected, actual)
-
end
-
end
-
-
# @private
-
1
def self.hashes_match?(expected_hash, actual_hash)
-
return false if expected_hash.size != actual_hash.size
-
-
expected_hash.all? do |expected_key, expected_value|
-
actual_value = actual_hash.fetch(expected_key) { return false }
-
values_match?(expected_value, actual_value)
-
end
-
end
-
-
1
private_class_method :arrays_match?, :hashes_match?
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# @private
-
1
def self.matcher_definitions
-
2
@matcher_definitions ||= []
-
end
-
-
# Used internally to break cyclic dependency between mocks, expectations,
-
# and support. We don't currently have a consistent implementation of our
-
# matchers, though we are considering changing that:
-
# https://github.com/rspec/rspec-mocks/issues/513
-
#
-
# @private
-
1
def self.register_matcher_definition(&block)
-
2
matcher_definitions << block
-
end
-
-
# Remove a previously registered matcher. Useful for cleaning up after
-
# yourself in specs.
-
#
-
# @private
-
1
def self.deregister_matcher_definition(&block)
-
matcher_definitions.delete(block)
-
end
-
-
# @private
-
1
def self.is_a_matcher?(object)
-
matcher_definitions.any? { |md| md.call(object) }
-
end
-
-
# @api private
-
#
-
# gives a string representation of an object for use in RSpec descriptions
-
1
def self.rspec_description_for_object(object)
-
if RSpec::Support.is_a_matcher?(object) && object.respond_to?(:description)
-
object.description
-
else
-
object
-
end
-
end
-
end
-
end
-
1
require 'rspec/support'
-
1
RSpec::Support.require_rspec_support "ruby_features"
-
1
RSpec::Support.require_rspec_support "matcher_definition"
-
-
1
module RSpec
-
1
module Support
-
# Extracts info about the number of arguments and allowed/required
-
# keyword args of a given method.
-
#
-
# @private
-
1
class MethodSignature
-
1
attr_reader :min_non_kw_args, :max_non_kw_args, :optional_kw_args, :required_kw_args
-
-
1
def initialize(method)
-
@method = method
-
@optional_kw_args = []
-
@required_kw_args = []
-
classify_parameters
-
end
-
-
1
def non_kw_args_arity_description
-
case max_non_kw_args
-
when min_non_kw_args then min_non_kw_args.to_s
-
when INFINITY then "#{min_non_kw_args} or more"
-
else "#{min_non_kw_args} to #{max_non_kw_args}"
-
end
-
end
-
-
1
def valid_non_kw_args?(positional_arg_count)
-
min_non_kw_args <= positional_arg_count &&
-
positional_arg_count <= max_non_kw_args
-
end
-
-
1
if RubyFeatures.optional_and_splat_args_supported?
-
1
def description
-
@description ||= begin
-
parts = []
-
-
unless non_kw_args_arity_description == "0"
-
parts << "arity of #{non_kw_args_arity_description}"
-
end
-
-
if @optional_kw_args.any?
-
parts << "optional keyword args (#{@optional_kw_args.map(&:inspect).join(", ")})"
-
end
-
-
if @required_kw_args.any?
-
parts << "required keyword args (#{@required_kw_args.map(&:inspect).join(", ")})"
-
end
-
-
parts << "any additional keyword args" if @allows_any_kw_args
-
-
parts.join(" and ")
-
end
-
end
-
-
1
def missing_kw_args_from(given_kw_args)
-
@required_kw_args - given_kw_args
-
end
-
-
1
def invalid_kw_args_from(given_kw_args)
-
return [] if @allows_any_kw_args
-
given_kw_args - @allowed_kw_args
-
end
-
-
1
def has_kw_args_in?(args)
-
Hash === args.last && could_contain_kw_args?(args)
-
end
-
-
# Without considering what the last arg is, could it
-
# contain keyword arguments?
-
1
def could_contain_kw_args?(args)
-
return false if args.count <= min_non_kw_args
-
@allows_any_kw_args || @allowed_kw_args.any?
-
end
-
-
1
def classify_parameters
-
optional_non_kw_args = @min_non_kw_args = 0
-
@allows_any_kw_args = false
-
-
@method.parameters.each do |(type, name)|
-
case type
-
# def foo(a:)
-
when :keyreq then @required_kw_args << name
-
# def foo(a: 1)
-
when :key then @optional_kw_args << name
-
# def foo(**kw_args)
-
when :keyrest then @allows_any_kw_args = true
-
# def foo(a)
-
when :req then @min_non_kw_args += 1
-
# def foo(a = 1)
-
when :opt then optional_non_kw_args += 1
-
# def foo(*a)
-
when :rest then optional_non_kw_args = INFINITY
-
end
-
end
-
-
@max_non_kw_args = @min_non_kw_args + optional_non_kw_args
-
@allowed_kw_args = @required_kw_args + @optional_kw_args
-
end
-
else
-
def description
-
"arity of #{non_kw_args_arity_description}"
-
end
-
-
def missing_kw_args_from(_given_kw_args)
-
[]
-
end
-
-
def invalid_kw_args_from(_given_kw_args)
-
[]
-
end
-
-
def has_kw_args_in?(_args)
-
false
-
end
-
-
def could_contain_kw_args?(*)
-
false
-
end
-
-
def classify_parameters
-
arity = @method.arity
-
if arity < 0
-
# `~` inverts the one's complement and gives us the
-
# number of required args
-
@min_non_kw_args = ~arity
-
@max_non_kw_args = INFINITY
-
else
-
@min_non_kw_args = arity
-
@max_non_kw_args = arity
-
end
-
end
-
end
-
-
1
INFINITY = 1 / 0.0
-
end
-
-
# Deals with the slightly different semantics of block arguments.
-
# For methods, arguments are required unless a default value is provided.
-
# For blocks, arguments are optional, even if no default value is provided.
-
#
-
# However, we want to treat block args as required since you virtually
-
# always want to pass a value for each received argument and our
-
# `and_yield` has treated block args as required for many years.
-
#
-
# @api private
-
1
class BlockSignature < MethodSignature
-
1
if RubyFeatures.optional_and_splat_args_supported?
-
1
def classify_parameters
-
super
-
@min_non_kw_args = @max_non_kw_args unless @max_non_kw_args == INFINITY
-
end
-
end
-
end
-
-
# Abstract base class for signature verifiers.
-
#
-
# @api private
-
1
class MethodSignatureVerifier
-
1
attr_reader :non_kw_args, :kw_args
-
-
1
def initialize(signature, args)
-
@signature = signature
-
@non_kw_args, @kw_args = split_args(*args)
-
end
-
-
1
def valid?
-
missing_kw_args.empty? &&
-
invalid_kw_args.empty? &&
-
valid_non_kw_args?
-
end
-
-
1
def error_message
-
if missing_kw_args.any?
-
"Missing required keyword arguments: %s" % [
-
missing_kw_args.join(", ")
-
]
-
elsif invalid_kw_args.any?
-
"Invalid keyword arguments provided: %s" % [
-
invalid_kw_args.join(", ")
-
]
-
elsif !valid_non_kw_args?
-
"Wrong number of arguments. Expected %s, got %s." % [
-
@signature.non_kw_args_arity_description,
-
non_kw_args.length
-
]
-
end
-
end
-
-
1
private
-
-
1
def valid_non_kw_args?
-
@signature.valid_non_kw_args?(non_kw_args.length)
-
end
-
-
1
def missing_kw_args
-
@signature.missing_kw_args_from(kw_args)
-
end
-
-
1
def invalid_kw_args
-
@signature.invalid_kw_args_from(kw_args)
-
end
-
-
1
def split_args(*args)
-
kw_args = if @signature.has_kw_args_in?(args)
-
args.pop.keys
-
else
-
[]
-
end
-
-
[args, kw_args]
-
end
-
end
-
-
# Figures out wether a given method can accept various arguments.
-
# Surprisingly non-trivial.
-
#
-
# @private
-
1
StrictSignatureVerifier = MethodSignatureVerifier
-
-
# Allows matchers to be used instead of providing keyword arguments. In
-
# practice, when this happens only the arity of the method is verified.
-
#
-
# @private
-
1
class LooseSignatureVerifier < MethodSignatureVerifier
-
1
private
-
-
1
def split_args(*args)
-
if RSpec::Support.is_a_matcher?(args.last) && @signature.could_contain_kw_args?(args)
-
args.pop
-
@signature = SignatureWithKeywordArgumentsMatcher.new(@signature)
-
end
-
-
super(*args)
-
end
-
-
# If a matcher is used in a signature in place of keyword arguments, all
-
# keyword argument validation needs to be skipped since the matcher is
-
# opaque.
-
#
-
# Instead, keyword arguments will be validated when the method is called
-
# and they are actually known.
-
#
-
# @private
-
1
class SignatureWithKeywordArgumentsMatcher
-
1
def initialize(signature)
-
@signature = signature
-
end
-
-
1
def missing_kw_args_from(_kw_args)
-
[]
-
end
-
-
1
def invalid_kw_args_from(_kw_args)
-
[]
-
end
-
-
1
def non_kw_args_arity_description
-
@signature.non_kw_args_arity_description
-
end
-
-
1
def valid_non_kw_args?(*args)
-
@signature.valid_non_kw_args?(*args)
-
end
-
-
1
def has_kw_args_in?(args)
-
@signature.has_kw_args_in?(args)
-
end
-
end
-
end
-
end
-
end
-
1
module RSpec
-
1
module Support
-
# Provide additional output details beyond what `inspect` provides when
-
# printing Time, DateTime, or BigDecimal
-
1
module ObjectFormatter
-
# @api private
-
1
def self.format(object)
-
prepare_for_inspection(object).inspect
-
end
-
-
# rubocop:disable MethodLength
-
-
# @private
-
# Prepares the provided object to be formatted by wrapping it as needed
-
# in something that, when `inspect` is called on it, will produce the
-
# desired output.
-
#
-
# This allows us to apply the desired formatting to hash/array data structures
-
# at any level of nesting, simply by walking that structure and replacing items
-
# with custom items that have `inspect` defined to return the desired output
-
# for that item. Then we can just use `Array#inspect` or `Hash#inspect` to
-
# format the entire thing.
-
1
def self.prepare_for_inspection(object)
-
case object
-
when Array
-
return object.map { |o| prepare_for_inspection(o) }
-
when Hash
-
return prepare_hash(object)
-
when Time
-
inspection = format_time(object)
-
else
-
if defined?(DateTime) && DateTime === object
-
inspection = format_date_time(object)
-
elsif defined?(BigDecimal) && BigDecimal === object
-
inspection = "#{object.to_s 'F'} (#{object.inspect})"
-
elsif RSpec::Support.is_a_matcher?(object) && object.respond_to?(:description)
-
inspection = object.description
-
else
-
return object
-
end
-
end
-
-
InspectableItem.new(inspection)
-
end
-
# rubocop:enable MethodLength
-
-
# @private
-
1
def self.prepare_hash(input)
-
input.inject({}) do |hash, (k, v)|
-
hash[prepare_for_inspection(k)] = prepare_for_inspection(v)
-
hash
-
end
-
end
-
-
1
TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
-
-
1
if Time.method_defined?(:nsec)
-
# @private
-
1
def self.format_time(time)
-
time.strftime("#{TIME_FORMAT}.#{"%09d" % time.nsec} %z")
-
end
-
else # for 1.8.7
-
# @private
-
def self.format_time(time)
-
time.strftime("#{TIME_FORMAT}.#{"%06d" % time.usec} %z")
-
end
-
end
-
-
1
DATE_TIME_FORMAT = "%a, %d %b %Y %H:%M:%S.%N %z"
-
# ActiveSupport sometimes overrides inspect. If `ActiveSupport` is
-
# defined use a custom format string that includes more time precision.
-
# @private
-
1
def self.format_date_time(date_time)
-
if defined?(ActiveSupport)
-
date_time.strftime(DATE_TIME_FORMAT)
-
else
-
date_time.inspect
-
end
-
end
-
-
# @private
-
1
InspectableItem = Struct.new(:inspection) do
-
1
def inspect
-
inspection
-
end
-
-
1
def pretty_print(pp)
-
pp.text inspection
-
end
-
end
-
end
-
end
-
end